Merge "Replace reindexAfterRefUpdate setting with indexMergeable"
diff --git a/Documentation/dev-plugins.txt b/Documentation/dev-plugins.txt
index 223ec71..b7b807f 100644
--- a/Documentation/dev-plugins.txt
+++ b/Documentation/dev-plugins.txt
@@ -452,8 +452,8 @@
 [source,java]
 ----
 import com.google.gerrit.common.EventDispatcher;
+import com.google.gerrit.exceptions.StorageException;
 import com.google.gerrit.extensions.registration.DynamicItem;
-import com.google.exceptions.StorageException;
 import com.google.inject.Inject;
 
 class MyPlugin {
diff --git a/Documentation/licenses.txt b/Documentation/licenses.txt
index 18501b3..394886d 100644
--- a/Documentation/licenses.txt
+++ b/Documentation/licenses.txt
@@ -88,6 +88,8 @@
 * openid:xerces
 * polymer_externs:polymer_closure
 * blame-cache
+* caffeine
+* caffeine-guava
 * gson
 * guava
 * guava-failureaccess
diff --git a/Documentation/rest-api-accounts.txt b/Documentation/rest-api-accounts.txt
index 7097a16..6ef4e20 100644
--- a/Documentation/rest-api-accounts.txt
+++ b/Documentation/rest-api-accounts.txt
@@ -2143,7 +2143,7 @@
 * a user name ("username")
 
 In all cases, accounts that are not
-link:config-gerrit.txt#accounts.visibility[visible] to the calling user are not
+link:config-gerrit.html#accounts.visibility[visible] to the calling user are not
 considered.
 
 In all cases _except_ a bare account ID and `self`/`me`, inactive accounts are
diff --git a/Documentation/rest-api-groups.txt b/Documentation/rest-api-groups.txt
index 00fd81f..a99c3bb 100644
--- a/Documentation/rest-api-groups.txt
+++ b/Documentation/rest-api-groups.txt
@@ -341,24 +341,19 @@
 [[query-groups]]
 === Query Groups
 --
-'GET /groups/?query2=<query>'
+'GET /groups/?query=<query>'
 --
 
 Queries internal groups visible to the caller. The
 link:user-search-groups.html#_search_operators[query string] must be
-provided by the `query2` parameter. The `start` and `limit` parameters
+provided by the `query` parameter. The `start` and `limit` parameters
 can be used to skip/limit results.
 
 As result a list of link:#group-info[GroupInfo] entities is returned.
 
-[NOTE] `query2` is a temporary name and in future this option may be
-renamed to `query`. `query2` was chosen to maintain backwards
-compatibility with the deprecated `query` parameter on the
-link:#list-groups[List Groups] endpoint.
-
 .Request
 ----
-  GET /groups/?query2=inname:test HTTP/1.0
+  GET /groups/?query=inname:test HTTP/1.0
 ----
 
 .Response
@@ -398,12 +393,12 @@
 
 [[group-query-limit]]
 ==== Group Limit
-The `/groups/?query2=<query>` URL also accepts a limit integer in the
+The `/groups/?query=<query>` URL also accepts a limit integer in the
 `limit` parameter. This limits the results to `limit` groups.
 
 Query the first 25 groups in group list.
 ----
-  GET /groups/?query2=<query>&limit=25 HTTP/1.0
+  GET /groups/?query=<query>&limit=25 HTTP/1.0
 ----
 
 The `/groups/` URL also accepts a start integer in the `start`
@@ -411,7 +406,7 @@
 
 Query 25 groups starting from index 50.
 ----
-  GET /groups/?query2=<query>&limit=25&start=50 HTTP/1.0
+  GET /groups/?query=<query>&limit=25&start=50 HTTP/1.0
 ----
 
 [[group-query-options]]
diff --git a/Documentation/rest-api-projects.txt b/Documentation/rest-api-projects.txt
index 74ec8d3..c373b96 100644
--- a/Documentation/rest-api-projects.txt
+++ b/Documentation/rest-api-projects.txt
@@ -3233,6 +3233,66 @@
   HTTP/1.1 204 No Content
 ----
 
+[[batch-update-labels]]
+=== Batch Update Labels
+--
+'POST /projects/link:#project-name[\{project-name\}]/labels/'
+--
+
+Creates/updates/deletes multiple label definitions in this project at once.
+
+The calling user must have write access to the `refs/meta/config` branch of the
+project.
+
+The updates must be specified in the request body as
+link:#batch-label-input[BatchLabelInput] entity.
+
+The updates are processed in the following order:
+
+1. label deletions
+2. label creations
+3. label updates
+
+.Request
+----
+  POST /projects/My-Project/labels/ HTTP/1.0
+  Content-Type: application/json; charset=UTF-8
+
+  {
+    "commit_message": "Update Labels",
+    "delete": [
+      "Old-Review",
+      "Unused-Review"
+    ],
+    "create": [
+      {
+        "name": "Foo-Review",
+        "values": {
+          " 0": "No score",
+          "-1": "I would prefer this is not merged as is",
+          "-2": "This shall not be merged",
+          "+1": "Looks good to me, but someone else must approve",
+          "+2": "Looks good to me, approved"
+      }
+    ],
+    "update:" {
+      "Bar-Review": {
+        "function": "MaxWithBlock"
+      },
+      "Baz-Review": {
+        "copy_min_score": true
+      }
+    }
+  }
+----
+
+If the label updates were done successfully the response is "`200 OK`".
+
+.Response
+----
+  HTTP/1.1 200 OK
+----
+
 
 [[ids]]
 == IDs
@@ -3645,8 +3705,6 @@
 |`inherited_value` |optional|
 The inherited value of the configuration parameter, only set if
 `inheritable` is true.
-|`permitted_values` |optional|
-The list of permitted values, only set if the `type` is `LIST`.
 |===============================
 
 [[dashboard-info]]
@@ -3876,9 +3934,14 @@
 |Field Name      ||Description
 |`commit_message`|optional|
 Message that should be used to commit the change of the label in the
-`project.config` file to the `refs/meta/config` branch.
+`project.config` file to the `refs/meta/config` branch.+
+Must not be set if this `LabelDefinitionInput` entity is contained in a
+link:#batch-label-input[BatchLabelInput] entity.
 |`name`          |optional|
-The new link:config-labels.html#label_name[name] of the label.
+The new link:config-labels.html#label_name[name] of the label.+
+For label creation the name is required if this `LabelDefinitionInput` entity
+is contained in a link:#batch-label-input[BatchLabelInput]
+entity.
 |`function`      |optional|
 The new link:config-labels.html#label_function[function] of the label (can be
 `MaxWithBlock`, `AnyWithBlock`, `MaxNoBlock`, `NoBlock`, `NoOp` and `PatchSetLock`.
@@ -3961,6 +4024,27 @@
 Not set if not inherited or overridden.
 |===============================
 
+[[batch-label-input]]
+=== BatchLabelInput
+The `BatchLabelInput` entity contains information for batch updating label
+definitions in a project.
+
+[options="header",cols="1,^2,4"]
+|=============================
+|Field Name      ||Description
+|`commit_message`|optional|
+Message that should be used to commit the label updates in the
+`project.config` file to the `refs/meta/config` branch.
+|`delete`        |optional|
+List of labels that should be deleted.
+|`create`        |optional|
+List of link:#label-definition-input[LabelDefinitionInput] entities that
+describe labels that should be created.
+|`update`        |optional|
+Map of label names to link:#label-definition-input[LabelDefinitionInput]
+entities that describe the updates that should be done for the labels.
+|=============================
+
 [[project-access-input]]
 === ProjectAccessInput
 The `ProjectAccessInput` describes changes that should be applied to a project
diff --git a/WORKSPACE b/WORKSPACE
index 4e2c970..7529d8a 100644
--- a/WORKSPACE
+++ b/WORKSPACE
@@ -241,6 +241,31 @@
     sha1 = "1dcf1de382a0bf95a3d8b0849546c88bac1292c9",
 )
 
+CAFFEINE_VERS = "2.8.0"
+
+maven_jar(
+    name = "caffeine",
+    artifact = "com.github.ben-manes.caffeine:caffeine:" + CAFFEINE_VERS,
+    sha1 = "6000774d7f8412ced005a704188ced78beeed2bb",
+)
+
+# TODO(davido): Rename guava.jar to caffeine-guava.jar on fetch to prevent potential
+# naming collision between caffeine guava adapater and guava library itself.
+# Remove this renaming procedure, once this upstream issue is fixed:
+# https://github.com/ben-manes/caffeine/issues/364.
+http_file(
+    name = "caffeine-guava-renamed",
+    downloaded_file_path = "caffeine-guava-" + CAFFEINE_VERS + ".jar",
+    sha256 = "3a66ee3ec70971dee0bae6e56bda7b8742bc4bedd7489161bfbbaaf7137d89e1",
+    urls = [
+        "https://repo1.maven.org/maven2/com/github/ben-manes/caffeine/guava/" +
+        CAFFEINE_VERS +
+        "/guava-" +
+        CAFFEINE_VERS +
+        ".jar",
+    ],
+)
+
 maven_jar(
     name = "jsch",
     artifact = "com.jcraft:jsch:0.1.54",
diff --git a/java/com/google/gerrit/acceptance/AbstractDaemonTest.java b/java/com/google/gerrit/acceptance/AbstractDaemonTest.java
index 7edb43a..fd61aa5 100644
--- a/java/com/google/gerrit/acceptance/AbstractDaemonTest.java
+++ b/java/com/google/gerrit/acceptance/AbstractDaemonTest.java
@@ -644,6 +644,14 @@
     return result;
   }
 
+  protected PushOneCommit.Result createChange(TestRepository<InMemoryRepository> repo)
+      throws Exception {
+    PushOneCommit push = pushFactory.create(admin.newIdent(), repo);
+    PushOneCommit.Result result = push.to("refs/for/master");
+    result.assertOkStatus();
+    return result;
+  }
+
   protected PushOneCommit.Result createMergeCommitChange(String ref) throws Exception {
     return createMergeCommitChange(ref, "foo");
   }
diff --git a/java/com/google/gerrit/acceptance/GerritServer.java b/java/com/google/gerrit/acceptance/GerritServer.java
index e03e084..af8af90 100644
--- a/java/com/google/gerrit/acceptance/GerritServer.java
+++ b/java/com/google/gerrit/acceptance/GerritServer.java
@@ -269,7 +269,7 @@
 
   private static final ImmutableMap<String, Level> LOG_LEVELS =
       ImmutableMap.<String, Level>builder()
-          .put("com.google.gerrit", Level.DEBUG)
+          .put("com.google.gerrit", Level.INFO)
 
           // Silence non-critical messages from MINA SSHD.
           .put("org.apache.mina", Level.WARN)
diff --git a/java/com/google/gerrit/entities/Comment.java b/java/com/google/gerrit/entities/Comment.java
index 65c642c..55d739a 100644
--- a/java/com/google/gerrit/entities/Comment.java
+++ b/java/com/google/gerrit/entities/Comment.java
@@ -229,12 +229,6 @@
   public String serverId;
   public boolean unresolved;
 
-  /**
-   * Whether the comment was parsed from a JSON representation (false) or the legacy custom notes
-   * format (true).
-   */
-  public transient boolean legacyFormat;
-
   public Comment(Comment c) {
     this(
         new Key(c.key),
diff --git a/java/com/google/gerrit/extensions/api/projects/ProjectApi.java b/java/com/google/gerrit/extensions/api/projects/ProjectApi.java
index 6d02ec4..9873995 100644
--- a/java/com/google/gerrit/extensions/api/projects/ProjectApi.java
+++ b/java/com/google/gerrit/extensions/api/projects/ProjectApi.java
@@ -18,6 +18,7 @@
 import com.google.gerrit.extensions.api.access.ProjectAccessInput;
 import com.google.gerrit.extensions.api.config.AccessCheckInfo;
 import com.google.gerrit.extensions.api.config.AccessCheckInput;
+import com.google.gerrit.extensions.common.BatchLabelInput;
 import com.google.gerrit.extensions.common.ChangeInfo;
 import com.google.gerrit.extensions.common.LabelDefinitionInfo;
 import com.google.gerrit.extensions.common.ProjectInfo;
@@ -221,6 +222,13 @@
   LabelApi label(String labelName) throws RestApiException;
 
   /**
+   * Adds, updates and deletes label definitions in a batch.
+   *
+   * @param input input that describes additions, updates and deletions of label definitions
+   */
+  void labels(BatchLabelInput input) throws RestApiException;
+
+  /**
    * A default implementation which allows source compatibility when adding new methods to the
    * interface.
    */
@@ -404,5 +412,10 @@
     public LabelApi label(String labelName) throws RestApiException {
       throw new NotImplementedException();
     }
+
+    @Override
+    public void labels(BatchLabelInput input) throws RestApiException {
+      throw new NotImplementedException();
+    }
   }
 }
diff --git a/java/com/google/gerrit/extensions/common/BatchLabelInput.java b/java/com/google/gerrit/extensions/common/BatchLabelInput.java
new file mode 100644
index 0000000..eb4c581
--- /dev/null
+++ b/java/com/google/gerrit/extensions/common/BatchLabelInput.java
@@ -0,0 +1,26 @@
+// 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.extensions.common;
+
+import java.util.List;
+import java.util.Map;
+
+/** Input for the REST API that describes additions, updates and deletions of label definitions. */
+public class BatchLabelInput {
+  public String commitMessage;
+  public List<String> delete;
+  public List<LabelDefinitionInput> create;
+  public Map<String, LabelDefinitionInput> update;
+}
diff --git a/java/com/google/gerrit/index/query/QueryProcessor.java b/java/com/google/gerrit/index/query/QueryProcessor.java
index 9501e52..c05516b 100644
--- a/java/com/google/gerrit/index/query/QueryProcessor.java
+++ b/java/com/google/gerrit/index/query/QueryProcessor.java
@@ -18,7 +18,7 @@
 import static com.google.common.base.Preconditions.checkState;
 import static com.google.common.collect.ImmutableList.toImmutableList;
 import static com.google.common.flogger.LazyArgs.lazy;
-import static java.util.stream.Collectors.toSet;
+import static java.util.stream.Collectors.toList;
 
 import com.google.common.base.Throwables;
 import com.google.common.collect.ImmutableList;
@@ -272,7 +272,7 @@
         ImmutableList<T> matchesList = matches.get(i).toList();
         logger.atFine().log(
             "Matches[%d]:\n%s",
-            i, lazy(() -> matchesList.stream().map(this::formatForLogging).collect(toSet())));
+            i, lazy(() -> matchesList.stream().map(this::formatForLogging).collect(toList())));
         out.add(
             QueryResult.create(
                 queryStrings != null ? queryStrings.get(i) : null,
diff --git a/java/com/google/gerrit/index/query/RangeUtil.java b/java/com/google/gerrit/index/query/RangeUtil.java
index 1f22f36..cfe1929 100644
--- a/java/com/google/gerrit/index/query/RangeUtil.java
+++ b/java/com/google/gerrit/index/query/RangeUtil.java
@@ -106,6 +106,10 @@
         break;
     }
 
+    // Ensure that minValue <= min/max <= maxValue.
+    min = Ints.constrainToRange(min, minValue, maxValue);
+    max = Ints.constrainToRange(max, minValue, maxValue);
+
     return new Range(prefix, min, max);
   }
 
diff --git a/java/com/google/gerrit/server/api/projects/ProjectApiImpl.java b/java/com/google/gerrit/server/api/projects/ProjectApiImpl.java
index d7ab91b..6d7fc15 100644
--- a/java/com/google/gerrit/server/api/projects/ProjectApiImpl.java
+++ b/java/com/google/gerrit/server/api/projects/ProjectApiImpl.java
@@ -43,6 +43,7 @@
 import com.google.gerrit.extensions.api.projects.ProjectInput;
 import com.google.gerrit.extensions.api.projects.TagApi;
 import com.google.gerrit.extensions.api.projects.TagInfo;
+import com.google.gerrit.extensions.common.BatchLabelInput;
 import com.google.gerrit.extensions.common.ChangeInfo;
 import com.google.gerrit.extensions.common.Input;
 import com.google.gerrit.extensions.common.LabelDefinitionInfo;
@@ -77,6 +78,7 @@
 import com.google.gerrit.server.restapi.project.ListDashboards;
 import com.google.gerrit.server.restapi.project.ListLabels;
 import com.google.gerrit.server.restapi.project.ListTags;
+import com.google.gerrit.server.restapi.project.PostLabels;
 import com.google.gerrit.server.restapi.project.ProjectsCollection;
 import com.google.gerrit.server.restapi.project.PutConfig;
 import com.google.gerrit.server.restapi.project.PutDescription;
@@ -131,6 +133,7 @@
   private final Index index;
   private final IndexChanges indexChanges;
   private final Provider<ListLabels> listLabels;
+  private final PostLabels postLabels;
   private final LabelApiImpl.Factory labelApi;
 
   @AssistedInject
@@ -168,6 +171,7 @@
       Index index,
       IndexChanges indexChanges,
       Provider<ListLabels> listLabels,
+      PostLabels postLabels,
       LabelApiImpl.Factory labelApi,
       @Assisted ProjectResource project) {
     this(
@@ -205,6 +209,7 @@
         index,
         indexChanges,
         listLabels,
+        postLabels,
         labelApi,
         null);
   }
@@ -244,6 +249,7 @@
       Index index,
       IndexChanges indexChanges,
       Provider<ListLabels> listLabels,
+      PostLabels postLabels,
       LabelApiImpl.Factory labelApi,
       @Assisted String name) {
     this(
@@ -281,6 +287,7 @@
         index,
         indexChanges,
         listLabels,
+        postLabels,
         labelApi,
         name);
   }
@@ -320,6 +327,7 @@
       Index index,
       IndexChanges indexChanges,
       Provider<ListLabels> listLabels,
+      PostLabels postLabels,
       LabelApiImpl.Factory labelApi,
       String name) {
     this.permissionBackend = permissionBackend;
@@ -357,6 +365,7 @@
     this.index = index;
     this.indexChanges = indexChanges;
     this.listLabels = listLabels;
+    this.postLabels = postLabels;
     this.labelApi = labelApi;
   }
 
@@ -712,4 +721,13 @@
       throw asRestApiException("Cannot parse label", e);
     }
   }
+
+  @Override
+  public void labels(BatchLabelInput input) throws RestApiException {
+    try {
+      postLabels.apply(checkExists(), input);
+    } catch (Exception e) {
+      throw asRestApiException("Cannot update labels", e);
+    }
+  }
 }
diff --git a/java/com/google/gerrit/server/cache/CacheBackend.java b/java/com/google/gerrit/server/cache/CacheBackend.java
new file mode 100644
index 0000000..ec9876f
--- /dev/null
+++ b/java/com/google/gerrit/server/cache/CacheBackend.java
@@ -0,0 +1,25 @@
+// 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.server.cache;
+
+/** Caffeine is used as default cache backend, but can be overridden with Guava backend. */
+public enum CacheBackend {
+  CAFFEINE,
+  GUAVA;
+
+  public boolean isLegacyBackend() {
+    return this == GUAVA;
+  }
+}
diff --git a/java/com/google/gerrit/server/cache/CacheModule.java b/java/com/google/gerrit/server/cache/CacheModule.java
index 2878624..0fdc6f5 100644
--- a/java/com/google/gerrit/server/cache/CacheModule.java
+++ b/java/com/google/gerrit/server/cache/CacheModule.java
@@ -68,7 +68,8 @@
    */
   protected <K, V> CacheBinding<K, V> cache(
       String name, TypeLiteral<K> keyType, TypeLiteral<V> valType) {
-    CacheProvider<K, V> m = new CacheProvider<>(this, name, keyType, valType);
+    CacheProvider<K, V> m =
+        new CacheProvider<>(this, name, keyType, valType, CacheBackend.CAFFEINE);
     bindCache(m, name, keyType, valType);
     return m;
   }
@@ -123,7 +124,20 @@
    */
   protected <K, V> PersistentCacheBinding<K, V> persist(
       String name, Class<K> keyType, Class<V> valType) {
-    return persist(name, TypeLiteral.get(keyType), TypeLiteral.get(valType));
+    return persist(name, TypeLiteral.get(keyType), TypeLiteral.get(valType), CacheBackend.CAFFEINE);
+  }
+
+  /**
+   * Declare a named in-memory/on-disk cache.
+   *
+   * @param <K> type of key used to lookup entries.
+   * @param <V> type of value stored by the cache.
+   * @param backend cache backend.
+   * @return binding to describe the cache.
+   */
+  protected <K, V> PersistentCacheBinding<K, V> persist(
+      String name, Class<K> keyType, Class<V> valType, CacheBackend backend) {
+    return persist(name, TypeLiteral.get(keyType), TypeLiteral.get(valType), backend);
   }
 
   /**
@@ -135,7 +149,7 @@
    */
   protected <K, V> PersistentCacheBinding<K, V> persist(
       String name, Class<K> keyType, TypeLiteral<V> valType) {
-    return persist(name, TypeLiteral.get(keyType), valType);
+    return persist(name, TypeLiteral.get(keyType), valType, CacheBackend.CAFFEINE);
   }
 
   /**
@@ -146,8 +160,9 @@
    * @return binding to describe the cache.
    */
   protected <K, V> PersistentCacheBinding<K, V> persist(
-      String name, TypeLiteral<K> keyType, TypeLiteral<V> valType) {
-    PersistentCacheProvider<K, V> m = new PersistentCacheProvider<>(this, name, keyType, valType);
+      String name, TypeLiteral<K> keyType, TypeLiteral<V> valType, CacheBackend backend) {
+    PersistentCacheProvider<K, V> m =
+        new PersistentCacheProvider<>(this, name, keyType, valType, backend);
     bindCache(m, name, keyType, valType);
 
     Type cacheDefType =
diff --git a/java/com/google/gerrit/server/cache/CacheProvider.java b/java/com/google/gerrit/server/cache/CacheProvider.java
index b1a9b91..fe4244c 100644
--- a/java/com/google/gerrit/server/cache/CacheProvider.java
+++ b/java/com/google/gerrit/server/cache/CacheProvider.java
@@ -30,6 +30,7 @@
 
 class CacheProvider<K, V> implements Provider<Cache<K, V>>, CacheBinding<K, V>, CacheDef<K, V> {
   private final CacheModule module;
+  private final CacheBackend backend;
   final String name;
   private final TypeLiteral<K> keyType;
   private final TypeLiteral<V> valType;
@@ -44,11 +45,17 @@
   private MemoryCacheFactory memoryCacheFactory;
   private boolean frozen;
 
-  CacheProvider(CacheModule module, String name, TypeLiteral<K> keyType, TypeLiteral<V> valType) {
+  CacheProvider(
+      CacheModule module,
+      String name,
+      TypeLiteral<K> keyType,
+      TypeLiteral<V> valType,
+      CacheBackend backend) {
     this.module = module;
     this.name = name;
     this.keyType = keyType;
     this.valType = valType;
+    this.backend = backend;
   }
 
   @Inject(optional = true)
@@ -159,7 +166,9 @@
   public Cache<K, V> get() {
     freeze();
     CacheLoader<K, V> ldr = loader();
-    return ldr != null ? memoryCacheFactory.build(this, ldr) : memoryCacheFactory.build(this);
+    return ldr != null
+        ? memoryCacheFactory.build(this, ldr, backend)
+        : memoryCacheFactory.build(this, backend);
   }
 
   protected void checkNotFrozen() {
diff --git a/java/com/google/gerrit/server/cache/MemoryCacheFactory.java b/java/com/google/gerrit/server/cache/MemoryCacheFactory.java
index fc55753..558380d 100644
--- a/java/com/google/gerrit/server/cache/MemoryCacheFactory.java
+++ b/java/com/google/gerrit/server/cache/MemoryCacheFactory.java
@@ -19,7 +19,8 @@
 import com.google.common.cache.LoadingCache;
 
 public interface MemoryCacheFactory {
-  <K, V> Cache<K, V> build(CacheDef<K, V> def);
+  <K, V> Cache<K, V> build(CacheDef<K, V> def, CacheBackend backend);
 
-  <K, V> LoadingCache<K, V> build(CacheDef<K, V> def, CacheLoader<K, V> loader);
+  <K, V> LoadingCache<K, V> build(
+      CacheDef<K, V> def, CacheLoader<K, V> loader, CacheBackend backend);
 }
diff --git a/java/com/google/gerrit/server/cache/PersistentCacheFactory.java b/java/com/google/gerrit/server/cache/PersistentCacheFactory.java
index 27fa9ca..93f91ef 100644
--- a/java/com/google/gerrit/server/cache/PersistentCacheFactory.java
+++ b/java/com/google/gerrit/server/cache/PersistentCacheFactory.java
@@ -19,9 +19,10 @@
 import com.google.common.cache.LoadingCache;
 
 public interface PersistentCacheFactory {
-  <K, V> Cache<K, V> build(PersistentCacheDef<K, V> def);
+  <K, V> Cache<K, V> build(PersistentCacheDef<K, V> def, CacheBackend backend);
 
-  <K, V> LoadingCache<K, V> build(PersistentCacheDef<K, V> def, CacheLoader<K, V> loader);
+  <K, V> LoadingCache<K, V> build(
+      PersistentCacheDef<K, V> def, CacheLoader<K, V> loader, CacheBackend backend);
 
   void onStop(String plugin);
 }
diff --git a/java/com/google/gerrit/server/cache/PersistentCacheProvider.java b/java/com/google/gerrit/server/cache/PersistentCacheProvider.java
index 59d66e3..4fc107f 100644
--- a/java/com/google/gerrit/server/cache/PersistentCacheProvider.java
+++ b/java/com/google/gerrit/server/cache/PersistentCacheProvider.java
@@ -30,6 +30,7 @@
 
 class PersistentCacheProvider<K, V> extends CacheProvider<K, V>
     implements Provider<Cache<K, V>>, PersistentCacheBinding<K, V>, PersistentCacheDef<K, V> {
+  private final CacheBackend backend;
   private int version;
   private long diskLimit;
   private CacheSerializer<K> keySerializer;
@@ -39,9 +40,19 @@
 
   PersistentCacheProvider(
       CacheModule module, String name, TypeLiteral<K> keyType, TypeLiteral<V> valType) {
-    super(module, name, keyType, valType);
+    this(module, name, keyType, valType, CacheBackend.CAFFEINE);
+  }
+
+  PersistentCacheProvider(
+      CacheModule module,
+      String name,
+      TypeLiteral<K> keyType,
+      TypeLiteral<V> valType,
+      CacheBackend backend) {
+    super(module, name, keyType, valType, backend);
     version = -1;
     diskLimit = 128 << 20;
+    this.backend = backend;
   }
 
   @Inject(optional = true)
@@ -130,8 +141,8 @@
     freeze();
     CacheLoader<K, V> ldr = loader();
     return ldr != null
-        ? persistentCacheFactory.build(this, ldr)
-        : persistentCacheFactory.build(this);
+        ? persistentCacheFactory.build(this, ldr, backend)
+        : persistentCacheFactory.build(this, backend);
   }
 
   private static <T> void checkSerializer(
diff --git a/java/com/google/gerrit/server/cache/h2/H2CacheFactory.java b/java/com/google/gerrit/server/cache/h2/H2CacheFactory.java
index af1228d..2b068aa 100644
--- a/java/com/google/gerrit/server/cache/h2/H2CacheFactory.java
+++ b/java/com/google/gerrit/server/cache/h2/H2CacheFactory.java
@@ -21,6 +21,7 @@
 import com.google.common.util.concurrent.ThreadFactoryBuilder;
 import com.google.gerrit.extensions.events.LifecycleListener;
 import com.google.gerrit.extensions.registration.DynamicMap;
+import com.google.gerrit.server.cache.CacheBackend;
 import com.google.gerrit.server.cache.MemoryCacheFactory;
 import com.google.gerrit.server.cache.PersistentCacheDef;
 import com.google.gerrit.server.cache.PersistentCacheFactory;
@@ -156,18 +157,21 @@
 
   @SuppressWarnings({"unchecked"})
   @Override
-  public <K, V> Cache<K, V> build(PersistentCacheDef<K, V> in) {
+  public <K, V> Cache<K, V> build(PersistentCacheDef<K, V> in, CacheBackend backend) {
     long limit = config.getLong("cache", in.configKey(), "diskLimit", in.diskLimit());
 
     if (cacheDir == null || limit <= 0) {
-      return memCacheFactory.build(in);
+      return memCacheFactory.build(in, backend);
     }
 
     H2CacheDefProxy<K, V> def = new H2CacheDefProxy<>(in);
     SqlStore<K, V> store = newSqlStore(def, limit);
     H2CacheImpl<K, V> cache =
         new H2CacheImpl<>(
-            executor, store, def.keyType(), (Cache<K, ValueHolder<V>>) memCacheFactory.build(def));
+            executor,
+            store,
+            def.keyType(),
+            (Cache<K, ValueHolder<V>>) memCacheFactory.build(def, backend));
     synchronized (caches) {
       caches.add(cache);
     }
@@ -176,11 +180,12 @@
 
   @SuppressWarnings("unchecked")
   @Override
-  public <K, V> LoadingCache<K, V> build(PersistentCacheDef<K, V> in, CacheLoader<K, V> loader) {
+  public <K, V> LoadingCache<K, V> build(
+      PersistentCacheDef<K, V> in, CacheLoader<K, V> loader, CacheBackend backend) {
     long limit = config.getLong("cache", in.configKey(), "diskLimit", in.diskLimit());
 
     if (cacheDir == null || limit <= 0) {
-      return memCacheFactory.build(in, loader);
+      return memCacheFactory.build(in, loader, backend);
     }
 
     H2CacheDefProxy<K, V> def = new H2CacheDefProxy<>(in);
@@ -188,7 +193,9 @@
     Cache<K, ValueHolder<V>> mem =
         (Cache<K, ValueHolder<V>>)
             memCacheFactory.build(
-                def, (CacheLoader<K, V>) new H2CacheImpl.Loader<>(executor, store, loader));
+                def,
+                (CacheLoader<K, V>) new H2CacheImpl.Loader<>(executor, store, loader),
+                backend);
     H2CacheImpl<K, V> cache = new H2CacheImpl<>(executor, store, def.keyType(), mem);
     synchronized (caches) {
       caches.add(cache);
diff --git a/java/com/google/gerrit/server/cache/mem/BUILD b/java/com/google/gerrit/server/cache/mem/BUILD
index d805e1f..a666df7 100644
--- a/java/com/google/gerrit/server/cache/mem/BUILD
+++ b/java/com/google/gerrit/server/cache/mem/BUILD
@@ -8,6 +8,8 @@
         "//java/com/google/gerrit/common:annotations",
         "//java/com/google/gerrit/extensions:api",
         "//java/com/google/gerrit/server",
+        "//lib:caffeine",
+        "//lib:caffeine-guava",
         "//lib:guava",
         "//lib:jgit",
         "//lib/guice",
diff --git a/java/com/google/gerrit/server/cache/mem/DefaultMemoryCacheFactory.java b/java/com/google/gerrit/server/cache/mem/DefaultMemoryCacheFactory.java
index f76b8db..9906b3d 100644
--- a/java/com/google/gerrit/server/cache/mem/DefaultMemoryCacheFactory.java
+++ b/java/com/google/gerrit/server/cache/mem/DefaultMemoryCacheFactory.java
@@ -17,13 +17,18 @@
 import static java.util.concurrent.TimeUnit.NANOSECONDS;
 import static java.util.concurrent.TimeUnit.SECONDS;
 
+import com.github.benmanes.caffeine.cache.Caffeine;
+import com.github.benmanes.caffeine.cache.RemovalListener;
+import com.github.benmanes.caffeine.cache.Weigher;
+import com.github.benmanes.caffeine.guava.CaffeinatedGuava;
 import com.google.common.base.Strings;
 import com.google.common.cache.Cache;
 import com.google.common.cache.CacheBuilder;
 import com.google.common.cache.CacheLoader;
 import com.google.common.cache.LoadingCache;
-import com.google.common.cache.Weigher;
+import com.google.common.cache.RemovalNotification;
 import com.google.gerrit.common.Nullable;
+import com.google.gerrit.server.cache.CacheBackend;
 import com.google.gerrit.server.cache.CacheDef;
 import com.google.gerrit.server.cache.ForwardingRemovalListener;
 import com.google.gerrit.server.cache.MemoryCacheFactory;
@@ -46,25 +51,30 @@
   }
 
   @Override
-  public <K, V> Cache<K, V> build(CacheDef<K, V> def) {
-    return create(def).build();
+  public <K, V> Cache<K, V> build(CacheDef<K, V> def, CacheBackend backend) {
+    return backend.isLegacyBackend()
+        ? createLegacy(def).build()
+        : CaffeinatedGuava.build(create(def));
   }
 
   @Override
-  public <K, V> LoadingCache<K, V> build(CacheDef<K, V> def, CacheLoader<K, V> loader) {
-    return create(def).build(loader);
+  public <K, V> LoadingCache<K, V> build(
+      CacheDef<K, V> def, CacheLoader<K, V> loader, CacheBackend backend) {
+    return backend.isLegacyBackend()
+        ? createLegacy(def).build(loader)
+        : CaffeinatedGuava.build(create(def), loader);
   }
 
   @SuppressWarnings("unchecked")
-  private <K, V> CacheBuilder<K, V> create(CacheDef<K, V> def) {
-    CacheBuilder<K, V> builder = newCacheBuilder();
+  private <K, V> CacheBuilder<K, V> createLegacy(CacheDef<K, V> def) {
+    CacheBuilder<K, V> builder = newLegacyCacheBuilder();
     builder.recordStats();
     builder.maximumWeight(
         cfg.getLong("cache", def.configKey(), "memoryLimit", def.maximumWeight()));
 
     builder = builder.removalListener(forwardingRemovalListenerFactory.create(def.name()));
 
-    Weigher<K, V> weigher = def.weigher();
+    com.google.common.cache.Weigher<K, V> weigher = def.weigher();
     if (weigher == null) {
       weigher = unitWeight();
     }
@@ -98,6 +108,42 @@
     return builder;
   }
 
+  private <K, V> Caffeine<K, V> create(CacheDef<K, V> def) {
+    Caffeine<K, V> builder = newCacheBuilder();
+    builder.recordStats();
+    builder.maximumWeight(
+        cfg.getLong("cache", def.configKey(), "memoryLimit", def.maximumWeight()));
+    builder = builder.removalListener(newRemovalListener(def.name()));
+    builder.weigher(newWeigher(def.weigher()));
+
+    Duration expireAfterWrite = def.expireAfterWrite();
+    if (has(def.configKey(), "maxAge")) {
+      builder.expireAfterWrite(
+          ConfigUtil.getTimeUnit(
+              cfg, "cache", def.configKey(), "maxAge", toSeconds(expireAfterWrite), SECONDS),
+          SECONDS);
+    } else if (expireAfterWrite != null) {
+      builder.expireAfterWrite(expireAfterWrite.toNanos(), NANOSECONDS);
+    }
+
+    Duration expireAfterAccess = def.expireFromMemoryAfterAccess();
+    if (has(def.configKey(), "expireFromMemoryAfterAccess")) {
+      builder.expireAfterAccess(
+          ConfigUtil.getTimeUnit(
+              cfg,
+              "cache",
+              def.configKey(),
+              "expireFromMemoryAfterAccess",
+              toSeconds(expireAfterAccess),
+              SECONDS),
+          SECONDS);
+    } else if (expireAfterAccess != null) {
+      builder.expireAfterAccess(expireAfterAccess.toNanos(), NANOSECONDS);
+    }
+
+    return builder;
+  }
+
   private static long toSeconds(@Nullable Duration duration) {
     return duration != null ? duration.getSeconds() : 0;
   }
@@ -107,11 +153,31 @@
   }
 
   @SuppressWarnings("unchecked")
-  private static <K, V> CacheBuilder<K, V> newCacheBuilder() {
+  private static <K, V> CacheBuilder<K, V> newLegacyCacheBuilder() {
     return (CacheBuilder<K, V>) CacheBuilder.newBuilder();
   }
 
-  private static <K, V> Weigher<K, V> unitWeight() {
+  private static <K, V> com.google.common.cache.Weigher<K, V> unitWeight() {
     return (key, value) -> 1;
   }
+
+  @SuppressWarnings("unchecked")
+  private static <K, V> Caffeine<K, V> newCacheBuilder() {
+    return (Caffeine<K, V>) Caffeine.newBuilder();
+  }
+
+  @SuppressWarnings("unchecked")
+  private <V, K> RemovalListener<K, V> newRemovalListener(String cacheName) {
+    return (k, v, cause) ->
+        forwardingRemovalListenerFactory
+            .create(cacheName)
+            .onRemoval(
+                RemovalNotification.create(
+                    k, v, com.google.common.cache.RemovalCause.valueOf(cause.name())));
+  }
+
+  private static <K, V> Weigher<K, V> newWeigher(
+      com.google.common.cache.Weigher<K, V> guavaWeigher) {
+    return guavaWeigher == null ? Weigher.singletonWeigher() : (k, v) -> guavaWeigher.weigh(k, v);
+  }
 }
diff --git a/java/com/google/gerrit/server/change/ChangeInserter.java b/java/com/google/gerrit/server/change/ChangeInserter.java
index 1bf5103..4263373 100644
--- a/java/com/google/gerrit/server/change/ChangeInserter.java
+++ b/java/com/google/gerrit/server/change/ChangeInserter.java
@@ -55,6 +55,7 @@
 import com.google.gerrit.server.git.GroupCollector;
 import com.google.gerrit.server.git.validators.CommitValidationException;
 import com.google.gerrit.server.git.validators.CommitValidators;
+import com.google.gerrit.server.logging.RequestId;
 import com.google.gerrit.server.mail.send.CreateChangeSender;
 import com.google.gerrit.server.notedb.ChangeUpdate;
 import com.google.gerrit.server.patch.PatchSetInfoFactory;
@@ -386,7 +387,7 @@
         psUtil.insert(
             ctx.getRevWalk(), update, psId, commitId, newGroups, pushCert, patchSetDescription);
 
-    /* TODO: fixStatus is used here because the tests
+    /* TODO: fixStatusToMerged is used here because the tests
      * (byStatusClosed() in AbstractQueryChangesTest)
      * insert changes that are already merged,
      * and setStatus may not be used to set the Status to merged
@@ -394,7 +395,11 @@
      * is it possible to make the tests use the merge code path,
      * instead of setting the status directly?
      */
-    update.fixStatus(change.getStatus());
+    if (change.getStatus() == Change.Status.MERGED) {
+      update.fixStatusToMerged(new RequestId(ctx.getChange().getId().toString()));
+    } else {
+      update.setStatus(change.getStatus());
+    }
 
     reviewerAdditions =
         reviewerAdder.prepare(ctx.getNotes(), ctx.getUser(), getReviewerInputs(), true);
diff --git a/java/com/google/gerrit/server/change/ChangeJson.java b/java/com/google/gerrit/server/change/ChangeJson.java
index 9937fd0..21ee28a 100644
--- a/java/com/google/gerrit/server/change/ChangeJson.java
+++ b/java/com/google/gerrit/server/change/ChangeJson.java
@@ -138,6 +138,7 @@
           COMMIT_FOOTERS,
           CURRENT_ACTIONS,
           CURRENT_COMMIT,
+          DETAILED_LABELS, // may need to load ChangeNotes to check remove reviewer permissions
           MESSAGES);
 
   @Singleton
diff --git a/java/com/google/gerrit/server/change/ConsistencyChecker.java b/java/com/google/gerrit/server/change/ConsistencyChecker.java
index 0374a1c..19db5ee 100644
--- a/java/com/google/gerrit/server/change/ConsistencyChecker.java
+++ b/java/com/google/gerrit/server/change/ConsistencyChecker.java
@@ -44,6 +44,7 @@
 import com.google.gerrit.server.PatchSetUtil;
 import com.google.gerrit.server.account.Accounts;
 import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.logging.RequestId;
 import com.google.gerrit.server.notedb.ChangeNotes;
 import com.google.gerrit.server.notedb.PatchSetState;
 import com.google.gerrit.server.patch.PatchSetInfoFactory;
@@ -576,7 +577,8 @@
     @Override
     public boolean updateChange(ChangeContext ctx) {
       ctx.getChange().setStatus(Change.Status.MERGED);
-      ctx.getUpdate(ctx.getChange().currentPatchSetId()).fixStatus(Change.Status.MERGED);
+      ctx.getUpdate(ctx.getChange().currentPatchSetId())
+          .fixStatusToMerged(new RequestId(ctx.getChange().getId().toString()));
       p.status = Status.FIXED;
       p.outcome = "Marked change as merged";
       return true;
diff --git a/java/com/google/gerrit/server/git/MergedByPushOp.java b/java/com/google/gerrit/server/git/MergedByPushOp.java
index 858a55a..c8b5e3f 100644
--- a/java/com/google/gerrit/server/git/MergedByPushOp.java
+++ b/java/com/google/gerrit/server/git/MergedByPushOp.java
@@ -26,6 +26,7 @@
 import com.google.gerrit.server.PatchSetUtil;
 import com.google.gerrit.server.config.SendEmailExecutor;
 import com.google.gerrit.server.extensions.events.ChangeMerged;
+import com.google.gerrit.server.logging.RequestId;
 import com.google.gerrit.server.mail.send.MergedSender;
 import com.google.gerrit.server.notedb.ChangeUpdate;
 import com.google.gerrit.server.patch.PatchSetInfoFactory;
@@ -132,9 +133,11 @@
     }
     change.setCurrentPatchSet(info);
     change.setStatus(Change.Status.MERGED);
+    RequestId submissionId = new RequestId(change.getId().toString());
+    change.setSubmissionId(submissionId.toStringForStorage());
     // we cannot reconstruct the submit records for when this change was
-    // submitted, this is why we must fix the status
-    update.fixStatus(Change.Status.MERGED);
+    // submitted, this is why we must fix the status and other details.
+    update.fixStatusToMerged(submissionId);
     update.setCurrentPatchSet();
     if (change.isWorkInProgress()) {
       change.setWorkInProgress(false);
diff --git a/java/com/google/gerrit/server/mail/send/OutgoingEmail.java b/java/com/google/gerrit/server/mail/send/OutgoingEmail.java
index a390de3..7d2fa0a 100644
--- a/java/com/google/gerrit/server/mail/send/OutgoingEmail.java
+++ b/java/com/google/gerrit/server/mail/send/OutgoingEmail.java
@@ -127,11 +127,19 @@
             // this message so they can always review and audit what we sent
             // on their behalf to others.
             //
+            logger.atFine().log(
+                "CC email sender %s because the email strategy of this user is %s",
+                fromUser.get().account().id(), CC_ON_OWN_COMMENTS);
             add(RecipientType.CC, fromId);
           } else if (!notify.accounts().containsValue(fromId) && rcptTo.remove(fromId)) {
             // If they don't want a copy, but we queued one up anyway,
             // drop them from the recipient lists.
             //
+            logger.atFine().log(
+                "Not CCing email sender %s because the email strategy of this user is not %s but %s",
+                fromUser.get().account().id(),
+                CC_ON_OWN_COMMENTS,
+                senderPrefs != null ? senderPrefs.getEmailStrategy() : null);
             removeUser(fromUser.get().account());
           }
         }
@@ -145,8 +153,12 @@
           Account thisUserAccount = thisUser.get().account();
           GeneralPreferencesInfo prefs = thisUser.get().generalPreferences();
           if (prefs == null || prefs.getEmailStrategy() == DISABLED) {
+            logger.atFine().log(
+                "Not emailing account %s because user has set email strategy to %s", id, DISABLED);
             removeUser(thisUserAccount);
           } else if (useHtml() && prefs.getEmailFormat() == EmailFormat.PLAINTEXT) {
+            logger.atFine().log(
+                "Removing account %s from HTML email because user prefers plain text emails", id);
             removeUser(thisUserAccount);
             smtpRcptToPlaintextOnly.add(
                 new Address(thisUserAccount.fullName(), thisUserAccount.preferredEmail()));
@@ -206,12 +218,13 @@
 
       if (!smtpRcptTo.isEmpty()) {
         // Send multipart message
-        logger.atFine().log("Sending multipart '%s'", messageClass);
+        logger.atFine().log(
+            "Sending multipart '%s' from %s to %s",
+            messageClass, va.smtpFromAddress, va.smtpRcptTo);
         args.emailSender.send(va.smtpFromAddress, va.smtpRcptTo, va.headers, va.body, va.htmlBody);
       }
 
       if (!smtpRcptToPlaintextOnly.isEmpty()) {
-        logger.atFine().log("Sending plaintext '%s'", messageClass);
         // Send plaintext message
         Map<String, EmailHeader> shallowCopy = new HashMap<>();
         shallowCopy.putAll(headers);
@@ -224,6 +237,9 @@
           to.add(a);
           shallowCopy.put(FieldName.TO, to);
         }
+        logger.atFine().log(
+            "Sending plaintext '%s' from %s to %s",
+            messageClass, va.smtpFromAddress, smtpRcptToPlaintextOnly);
         args.emailSender.send(va.smtpFromAddress, smtpRcptToPlaintextOnly, shallowCopy, va.body);
       }
     }
diff --git a/java/com/google/gerrit/server/notedb/AbstractChangeNotes.java b/java/com/google/gerrit/server/notedb/AbstractChangeNotes.java
index 0acf20e..9a1ba35 100644
--- a/java/com/google/gerrit/server/notedb/AbstractChangeNotes.java
+++ b/java/com/google/gerrit/server/notedb/AbstractChangeNotes.java
@@ -76,6 +76,7 @@
     }
   }
 
+  /** An {@link AutoCloseable} for parsing a single commit into ChangeNotesCommits. */
   public static class LoadHandle implements AutoCloseable {
     private final Repository repo;
     private final ObjectId id;
diff --git a/java/com/google/gerrit/server/notedb/ChangeDraftUpdate.java b/java/com/google/gerrit/server/notedb/ChangeDraftUpdate.java
index 971b537..7ca0b86 100644
--- a/java/com/google/gerrit/server/notedb/ChangeDraftUpdate.java
+++ b/java/com/google/gerrit/server/notedb/ChangeDraftUpdate.java
@@ -206,6 +206,7 @@
       cache.get(k.commitId()).deleteComment(k.key());
     }
 
+    // keyed by commit ID.
     Map<ObjectId, RevisionNoteBuilder> builders = cache.getBuilders();
     boolean touchedAnyRevs = false;
     boolean hasComments = false;
@@ -232,20 +233,13 @@
       return NO_OP_UPDATE;
     }
 
-    // If we touched every revision and there are no comments left, tell the
+    // If there are no comments left, tell the
     // caller to delete the entire ref.
-    boolean touchedAllRevs = updatedCommits.equals(rnm.revisionNotes.keySet());
-    if (touchedAllRevs && !hasComments) {
+    if (!rnm.noteMap.iterator().hasNext()) {
       return null;
     }
 
     ObjectId treeId = rnm.noteMap.writeTree(ins);
-    if (!rnm.noteMap.iterator().hasNext()) {
-      logger.atSevere().log(
-          "building draft update without content: hasComments=%s "
-              + "touchedAllRevs=%s updateCommits=%d revisionNotes=%d",
-          hasComments, touchedAllRevs, updatedCommits.size(), rnm.revisionNotes.size());
-    }
     cb.setTreeId(treeId);
     return cb;
   }
diff --git a/java/com/google/gerrit/server/notedb/ChangeNotesCommit.java b/java/com/google/gerrit/server/notedb/ChangeNotesCommit.java
index 78f6afc..71cb8c9 100644
--- a/java/com/google/gerrit/server/notedb/ChangeNotesCommit.java
+++ b/java/com/google/gerrit/server/notedb/ChangeNotesCommit.java
@@ -43,6 +43,8 @@
  * </ul>
  */
 public class ChangeNotesCommit extends RevCommit {
+
+  /** A {@link RevWalk} producing {@link ChangeNotesCommit}s. */
   public static ChangeNotesRevWalk newRevWalk(Repository repo) {
     return new ChangeNotesRevWalk(repo);
   }
@@ -62,6 +64,7 @@
     };
   }
 
+  /** A {@link RevWalk} that creates {@link ChangeNotesCommit}s rather than {@link RevCommit}s */
   public static class ChangeNotesRevWalk extends RevWalk {
     private ChangeNotesRevWalk(Repository repo) {
       super(repo);
diff --git a/java/com/google/gerrit/server/notedb/ChangeRevisionNote.java b/java/com/google/gerrit/server/notedb/ChangeRevisionNote.java
index b6443f1..4e52093 100644
--- a/java/com/google/gerrit/server/notedb/ChangeRevisionNote.java
+++ b/java/com/google/gerrit/server/notedb/ChangeRevisionNote.java
@@ -28,6 +28,7 @@
 import org.eclipse.jgit.lib.ObjectReader;
 import org.eclipse.jgit.util.MutableInteger;
 
+/** Implements the parsing of comment data, handling JSON decoding and push certificates. */
 class ChangeRevisionNote extends RevisionNote<Comment> {
   private final ChangeNoteJson noteJson;
   private final Comment.Status status;
diff --git a/java/com/google/gerrit/server/notedb/ChangeUpdate.java b/java/com/google/gerrit/server/notedb/ChangeUpdate.java
index c0cd173..02a4dcc 100644
--- a/java/com/google/gerrit/server/notedb/ChangeUpdate.java
+++ b/java/com/google/gerrit/server/notedb/ChangeUpdate.java
@@ -216,12 +216,14 @@
   }
 
   public void setStatus(Change.Status status) {
-    checkArgument(status != Change.Status.MERGED, "use merge(Iterable<SubmitRecord>)");
+    checkArgument(status != Change.Status.MERGED, "use merge(RequestId, Iterable<SubmitRecord>)");
     this.status = status;
   }
 
-  public void fixStatus(Change.Status status) {
-    this.status = status;
+  public void fixStatusToMerged(RequestId submissionId) {
+    checkArgument(submissionId != null, "submission id must be set for merged changes");
+    this.status = Change.Status.MERGED;
+    this.submissionId = submissionId.toStringForStorage();
   }
 
   public void putApproval(String label, short value) {
diff --git a/java/com/google/gerrit/server/notedb/RevisionNote.java b/java/com/google/gerrit/server/notedb/RevisionNote.java
index ff649a9..cd11e1b 100644
--- a/java/com/google/gerrit/server/notedb/RevisionNote.java
+++ b/java/com/google/gerrit/server/notedb/RevisionNote.java
@@ -26,6 +26,10 @@
 import org.eclipse.jgit.lib.ObjectReader;
 import org.eclipse.jgit.util.MutableInteger;
 
+/**
+ * Data stored in a note, parsed on demand. The data type to parse into is a generic list of type T.
+ * The source of the data is a array of raw bytes
+ */
 @UsedAt(UsedAt.Project.PLUGIN_CHECKS)
 public abstract class RevisionNote<T> {
   static final int MAX_NOTE_SZ = 25 << 20;
@@ -64,6 +68,7 @@
     return entities;
   }
 
+  /** Reads the raw data, and delegates parsing to the {@link #parse(byte[], int)} method. */
   public void parse() throws IOException, ConfigInvalidException {
     raw = reader.open(noteId, OBJ_BLOB).getCachedBytes(MAX_NOTE_SZ);
     MutableInteger p = new MutableInteger();
diff --git a/java/com/google/gerrit/server/notedb/RevisionNoteBuilder.java b/java/com/google/gerrit/server/notedb/RevisionNoteBuilder.java
index e63737c..81273dc 100644
--- a/java/com/google/gerrit/server/notedb/RevisionNoteBuilder.java
+++ b/java/com/google/gerrit/server/notedb/RevisionNoteBuilder.java
@@ -36,6 +36,7 @@
 import org.eclipse.jgit.lib.ObjectId;
 
 class RevisionNoteBuilder {
+  /** Construct a new RevisionNoteMap, seeding it with an existing (immutable) RevisionNoteMap */
   static class Cache {
     private final RevisionNoteMap<? extends RevisionNote<? extends Comment>> revisionNoteMap;
     private final Map<ObjectId, RevisionNoteBuilder> builders;
diff --git a/java/com/google/gerrit/server/notedb/RevisionNoteMap.java b/java/com/google/gerrit/server/notedb/RevisionNoteMap.java
index 3e1bad1..cf16073 100644
--- a/java/com/google/gerrit/server/notedb/RevisionNoteMap.java
+++ b/java/com/google/gerrit/server/notedb/RevisionNoteMap.java
@@ -25,8 +25,16 @@
 import org.eclipse.jgit.notes.Note;
 import org.eclipse.jgit.notes.NoteMap;
 
+/**
+ * A utility class that parses a NoteMap into commit => comment list data.
+ *
+ * @param <T> the RevisionNote for the comment type.
+ */
 class RevisionNoteMap<T extends RevisionNote<? extends Comment>> {
+  // CommitID => blob ID
   final NoteMap noteMap;
+
+  // CommitID => parsed data, immutable map.
   final ImmutableMap<ObjectId, T> revisionNotes;
 
   static RevisionNoteMap<ChangeRevisionNote> parse(
@@ -36,6 +44,7 @@
     for (Note note : noteMap) {
       ChangeRevisionNote rn = new ChangeRevisionNote(noteJson, reader, note.getData(), status);
       rn.parse();
+
       result.put(note.copy(), rn);
     }
     return new RevisionNoteMap<>(noteMap, ImmutableMap.copyOf(result));
diff --git a/java/com/google/gerrit/server/notedb/RobotCommentsRevisionNote.java b/java/com/google/gerrit/server/notedb/RobotCommentsRevisionNote.java
index 97a8ad4..fc4c9fd 100644
--- a/java/com/google/gerrit/server/notedb/RobotCommentsRevisionNote.java
+++ b/java/com/google/gerrit/server/notedb/RobotCommentsRevisionNote.java
@@ -26,6 +26,7 @@
 import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.lib.ObjectReader;
 
+/** Like {@link RevisionNote} but for robot comments. */
 public class RobotCommentsRevisionNote extends RevisionNote<RobotComment> {
   private final ChangeNoteJson noteUtil;
 
diff --git a/java/com/google/gerrit/server/patch/PatchListCacheImpl.java b/java/com/google/gerrit/server/patch/PatchListCacheImpl.java
index 6871652..15fa0f4 100644
--- a/java/com/google/gerrit/server/patch/PatchListCacheImpl.java
+++ b/java/com/google/gerrit/server/patch/PatchListCacheImpl.java
@@ -22,6 +22,7 @@
 import com.google.gerrit.entities.PatchSet;
 import com.google.gerrit.entities.Project;
 import com.google.gerrit.extensions.client.DiffPreferencesInfo.Whitespace;
+import com.google.gerrit.server.cache.CacheBackend;
 import com.google.gerrit.server.cache.CacheModule;
 import com.google.gerrit.server.config.GerritServerConfig;
 import com.google.inject.Inject;
@@ -45,7 +46,9 @@
       @Override
       protected void configure() {
         factory(PatchListLoader.Factory.class);
-        persist(FILE_NAME, PatchListKey.class, PatchList.class)
+        // TODO(davido): Switch off using legacy cache backend, after fixing PatchListLoader
+        // to be recursion free.
+        persist(FILE_NAME, PatchListKey.class, PatchList.class, CacheBackend.GUAVA)
             .maximumWeight(10 << 20)
             .weigher(PatchListWeigher.class);
 
diff --git a/java/com/google/gerrit/server/permissions/RefControl.java b/java/com/google/gerrit/server/permissions/RefControl.java
index 06fe471..378a512 100644
--- a/java/com/google/gerrit/server/permissions/RefControl.java
+++ b/java/com/google/gerrit/server/permissions/RefControl.java
@@ -440,8 +440,7 @@
     @Override
     public ForChange change(ChangeData cd) {
       try {
-        // TODO(hiesel) Force callers to call database() and use db instead of cd.db()
-        return getProjectControl().controlFor(cd.change()).asForChange(cd);
+        return getProjectControl().controlFor(cd.notes()).asForChange(cd);
       } catch (StorageException e) {
         return FailedPermissionBackend.change("unavailable", e);
       }
diff --git a/java/com/google/gerrit/server/restapi/account/QueryAccounts.java b/java/com/google/gerrit/server/restapi/account/QueryAccounts.java
index a40cdd0..f9e753c 100644
--- a/java/com/google/gerrit/server/restapi/account/QueryAccounts.java
+++ b/java/com/google/gerrit/server/restapi/account/QueryAccounts.java
@@ -43,6 +43,7 @@
 import com.google.gerrit.server.query.account.AccountQueryBuilder;
 import com.google.gerrit.server.query.account.AccountQueryProcessor;
 import com.google.inject.Inject;
+import com.google.inject.Provider;
 import java.util.Collections;
 import java.util.EnumSet;
 import java.util.LinkedHashMap;
@@ -58,12 +59,13 @@
   private final PermissionBackend permissionBackend;
   private final AccountLoader.Factory accountLoaderFactory;
   private final AccountQueryBuilder queryBuilder;
-  private final AccountQueryProcessor queryProcessor;
+  private final Provider<AccountQueryProcessor> queryProcessorProvider;
   private final boolean suggestConfig;
   private final int suggestFrom;
 
   private AccountLoader accountLoader;
   private boolean suggest;
+  private Integer limit;
   private int suggestLimit = 10;
   private String query;
   private Integer start;
@@ -80,7 +82,7 @@
       metaVar = "CNT",
       usage = "maximum number of users to return")
   public void setLimit(int n) {
-    queryProcessor.setUserProvidedLimit(n);
+    this.limit = n;
 
     if (n < 0) {
       suggestLimit = 10;
@@ -124,12 +126,12 @@
       PermissionBackend permissionBackend,
       AccountLoader.Factory accountLoaderFactory,
       AccountQueryBuilder queryBuilder,
-      AccountQueryProcessor queryProcessor,
+      Provider<AccountQueryProcessor> queryProcessorProvider,
       @GerritServerConfig Config cfg) {
     this.permissionBackend = permissionBackend;
     this.accountLoaderFactory = accountLoaderFactory;
     this.queryBuilder = queryBuilder;
-    this.queryProcessor = queryProcessor;
+    this.queryProcessorProvider = queryProcessorProvider;
     this.suggestFrom = cfg.getInt("suggest", null, "from", 0);
     this.options = EnumSet.noneOf(ListAccountsOption.class);
 
@@ -186,10 +188,15 @@
     }
     accountLoader = accountLoaderFactory.create(fillOptions);
 
+    AccountQueryProcessor queryProcessor = queryProcessorProvider.get();
     if (queryProcessor.isDisabled()) {
       throw new MethodNotAllowedException("query disabled");
     }
 
+    if (limit != null) {
+      queryProcessor.setUserProvidedLimit(limit);
+    }
+
     if (start != null) {
       queryProcessor.setStart(start);
     }
diff --git a/java/com/google/gerrit/server/restapi/change/GetDiff.java b/java/com/google/gerrit/server/restapi/change/GetDiff.java
index 74562c2..a6536ce 100644
--- a/java/com/google/gerrit/server/restapi/change/GetDiff.java
+++ b/java/com/google/gerrit/server/restapi/change/GetDiff.java
@@ -22,6 +22,7 @@
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Maps;
+import com.google.common.flogger.FluentLogger;
 import com.google.gerrit.common.data.PatchScript;
 import com.google.gerrit.common.data.PatchScript.DisplayMethod;
 import com.google.gerrit.entities.Patch;
@@ -73,6 +74,8 @@
 import org.kohsuke.args4j.spi.Setter;
 
 public class GetDiff implements RestReadView<FileResource> {
+  private static final FluentLogger logger = FluentLogger.forEnclosingClass();
+
   private static final ImmutableMap<Patch.ChangeType, ChangeType> CHANGE_TYPE =
       Maps.immutableEnumMap(
           new ImmutableMap.Builder<Patch.ChangeType, ChangeType>()
@@ -134,11 +137,17 @@
     }
     prefs.context = context;
     prefs.intralineDifference = intraline;
+    logger.atFine().log(
+        "diff preferences: ignoreWhitespace = %s, context = %s, intralineDifference = %s",
+        prefs.ignoreWhitespace, prefs.context, prefs.intralineDifference);
 
     PatchScriptFactory psf;
     PatchSet basePatchSet = null;
     PatchSet.Id pId = resource.getPatchKey().patchSetId();
     String fileName = resource.getPatchKey().fileName();
+    logger.atFine().log(
+        "patchSetId = %d, fileName = %s, base = %s, parentNum = %d",
+        pId.get(), fileName, base, parentNum);
     ChangeNotes notes = resource.getRevision().getNotes();
     if (base != null) {
       RevisionResource baseResource =
@@ -158,7 +167,10 @@
       ContentCollector contentCollector = new ContentCollector(ps);
       Set<Edit> editsDueToRebase = ps.getEditsDueToRebase();
       for (Edit edit : ps.getEdits()) {
+        logger.atFine().log("next edit = %s", edit);
+
         if (edit.getType() == Edit.Type.EMPTY) {
+          logger.atFine().log("skip empty edit");
           continue;
         }
         contentCollector.addCommon(edit.getBeginA());
@@ -197,6 +209,7 @@
           resource.getRevision().getEdit().isPresent()
               ? resource.getRevision().getEdit().get().getRefName()
               : resource.getRevision().getPatchSet().refName();
+      logger.atFine().log("revA = %s, revB = %s", revA, revB);
 
       ImmutableList<DiffWebLinkInfo> links =
           webLinks.getDiffLinks(
@@ -243,9 +256,11 @@
         } else {
           result.intralineStatus = IntraLineStatus.OK;
         }
+        logger.atFine().log("intralineStatus = %s", result.intralineStatus);
       }
 
       result.changeType = CHANGE_TYPE.get(ps.getChangeType());
+      logger.atFine().log("changeType = %s", result.changeType);
       if (result.changeType == null) {
         throw new IllegalStateException("unknown change type: " + ps.getChangeType());
       }
@@ -314,18 +329,29 @@
     }
 
     void addCommon(int end) {
+      logger.atFine().log("addCommon: end = %d", end);
+
       end = Math.min(end, fileA.size());
+      logger.atFine().log("end = %d", end);
+
       if (nextA >= end) {
+        logger.atFine().log("nextA >= end: nextA = %d, end = %d", nextA, end);
         return;
       }
 
       while (nextA < end) {
+        logger.atFine().log("nextA < end: nextA = %d, end = %d", nextA, end);
+
         if (!fileA.contains(nextA)) {
+          logger.atFine().log("fileA does not contain nextA: nextA = %d", nextA);
+
           int endRegion = Math.min(end, nextA == 0 ? fileA.first() : fileA.next(nextA - 1));
           int len = endRegion - nextA;
           entry().skip = len;
           nextA = endRegion;
           nextB += len;
+
+          logger.atFine().log("setting: nextA = %d, nextB = %d", nextA, nextB);
           continue;
         }
 
@@ -333,6 +359,7 @@
         for (int i = nextA; i == nextA && i < end; i = fileA.next(i), nextA++, nextB++) {
           if (ignoreWS && fileB.contains(nextB)) {
             if (e == null || e.common == null) {
+              logger.atFine().log("create new common entry: nextA = %d, nextB = %d", nextA, nextB);
               e = entry();
               e.a = Lists.newArrayListWithCapacity(end - nextA);
               e.b = Lists.newArrayListWithCapacity(end - nextA);
@@ -342,48 +369,71 @@
             e.b.add(fileB.get(nextB));
           } else {
             if (e == null || e.common != null) {
+              logger.atFine().log(
+                  "create new non-common entry: nextA = %d, nextB = %d", nextA, nextB);
               e = entry();
               e.ab = Lists.newArrayListWithCapacity(end - nextA);
             }
             e.ab.add(fileA.get(nextA));
           }
         }
+        logger.atFine().log("nextA = %d, nextB = %d", nextA, nextB);
       }
     }
 
     void addDiff(int endA, int endB, List<Edit> internalEdit, boolean dueToRebase) {
+      logger.atFine().log(
+          "addDiff: endA = %d, endB = %d, numberOfInternalEdits = %d, dueToRebase = %s",
+          endA, endB, internalEdit != null ? internalEdit.size() : 0, dueToRebase);
+
       int lenA = endA - nextA;
       int lenB = endB - nextB;
+      logger.atFine().log("lenA = %d, lenB = %d", lenA, lenB);
       checkState(lenA > 0 || lenB > 0);
 
+      logger.atFine().log("create non-common entry");
       ContentEntry e = entry();
       if (lenA > 0) {
+        logger.atFine().log("lenA > 0: lenA = %d", lenA);
         e.a = Lists.newArrayListWithCapacity(lenA);
         for (; nextA < endA; nextA++) {
           e.a.add(fileA.get(nextA));
         }
       }
       if (lenB > 0) {
+        logger.atFine().log("lenB > 0: lenB = %d", lenB);
         e.b = Lists.newArrayListWithCapacity(lenB);
         for (; nextB < endB; nextB++) {
           e.b.add(fileB.get(nextB));
         }
       }
       if (internalEdit != null && !internalEdit.isEmpty()) {
+        logger.atFine().log("processing internal edits");
+
         e.editA = Lists.newArrayListWithCapacity(internalEdit.size() * 2);
         e.editB = Lists.newArrayListWithCapacity(internalEdit.size() * 2);
         int lastA = 0;
         int lastB = 0;
         for (Edit edit : internalEdit) {
+          logger.atFine().log("internal edit = %s", edit);
+
           if (edit.getBeginA() != edit.getEndA()) {
+            logger.atFine().log(
+                "edit.getBeginA() != edit.getEndA(): edit.getBeginA() = %d, edit.getEndA() = %d",
+                edit.getBeginA(), edit.getEndA());
             e.editA.add(
                 ImmutableList.of(edit.getBeginA() - lastA, edit.getEndA() - edit.getBeginA()));
             lastA = edit.getEndA();
+            logger.atFine().log("lastA = %d", lastA);
           }
           if (edit.getBeginB() != edit.getEndB()) {
+            logger.atFine().log(
+                "edit.getBeginB() != edit.getEndB(): edit.getBeginB() = %d, edit.getEndB() = %d",
+                edit.getBeginB(), edit.getEndB());
             e.editB.add(
                 ImmutableList.of(edit.getBeginB() - lastB, edit.getEndB() - edit.getBeginB()));
             lastB = edit.getEndB();
+            logger.atFine().log("lastB = %d", lastB);
           }
         }
       }
diff --git a/java/com/google/gerrit/server/restapi/change/QueryChanges.java b/java/com/google/gerrit/server/restapi/change/QueryChanges.java
index 50e1e42..544177f 100644
--- a/java/com/google/gerrit/server/restapi/change/QueryChanges.java
+++ b/java/com/google/gerrit/server/restapi/change/QueryChanges.java
@@ -34,9 +34,11 @@
 import com.google.gerrit.server.query.change.ChangeQueryBuilder;
 import com.google.gerrit.server.query.change.ChangeQueryProcessor;
 import com.google.inject.Inject;
+import com.google.inject.Provider;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.EnumSet;
+import java.util.HashMap;
 import java.util.List;
 import org.kohsuke.args4j.Option;
 
@@ -45,8 +47,12 @@
 
   private final ChangeJson.Factory json;
   private final ChangeQueryBuilder qb;
-  private final ChangeQueryProcessor imp;
+  private final Provider<ChangeQueryProcessor> queryProcessorProvider;
+  private final HashMap<String, DynamicOptions.DynamicBean> dynamicBeans = new HashMap<>();
   private EnumSet<ListChangesOption> options;
+  private Integer limit;
+  private Integer start;
+  private Boolean noLimit;
 
   @Option(
       name = "--query",
@@ -61,7 +67,7 @@
       metaVar = "CNT",
       usage = "Maximum number of results to return")
   public void setLimit(int limit) {
-    imp.setUserProvidedLimit(limit);
+    this.limit = limit;
   }
 
   @Option(name = "-o", usage = "Output options per change")
@@ -80,24 +86,27 @@
       metaVar = "CNT",
       usage = "Number of changes to skip")
   public void setStart(int start) {
-    imp.setStart(start);
+    this.start = start;
   }
 
   @Option(name = "--no-limit", usage = "Return all results, overriding the default limit")
   public void setNoLimit(boolean on) {
-    imp.setNoLimit(on);
+    this.noLimit = on;
   }
 
   @Override
   public void setDynamicBean(String plugin, DynamicOptions.DynamicBean dynamicBean) {
-    imp.setDynamicBean(plugin, dynamicBean);
+    dynamicBeans.put(plugin, dynamicBean);
   }
 
   @Inject
-  QueryChanges(ChangeJson.Factory json, ChangeQueryBuilder qb, ChangeQueryProcessor qp) {
+  QueryChanges(
+      ChangeJson.Factory json,
+      ChangeQueryBuilder qb,
+      Provider<ChangeQueryProcessor> queryProcessorProvider) {
     this.json = json;
     this.qb = qb;
-    this.imp = qp;
+    this.queryProcessorProvider = queryProcessorProvider;
 
     options = EnumSet.noneOf(ListChangesOption.class);
   }
@@ -129,9 +138,22 @@
   }
 
   private List<List<ChangeInfo>> query() throws QueryParseException, PermissionBackendException {
-    if (imp.isDisabled()) {
+    ChangeQueryProcessor queryProcessor = queryProcessorProvider.get();
+    if (queryProcessor.isDisabled()) {
       throw new QueryParseException("query disabled");
     }
+
+    if (limit != null) {
+      queryProcessor.setUserProvidedLimit(limit);
+    }
+    if (start != null) {
+      queryProcessor.setStart(start);
+    }
+    if (noLimit != null) {
+      queryProcessor.setNoLimit(noLimit);
+    }
+    dynamicBeans.forEach((p, b) -> queryProcessor.setDynamicBean(p, b));
+
     if (queries == null || queries.isEmpty()) {
       queries = Collections.singletonList("status:open");
     } else if (queries.size() > 10) {
@@ -141,9 +163,9 @@
     }
 
     int cnt = queries.size();
-    List<QueryResult<ChangeData>> results = imp.query(qb.parse(queries));
+    List<QueryResult<ChangeData>> results = queryProcessor.query(qb.parse(queries));
     List<List<ChangeInfo>> res =
-        json.create(options, this.imp.getAttributesFactory()).format(results);
+        json.create(options, queryProcessor.getAttributesFactory()).format(results);
     for (int n = 0; n < cnt; n++) {
       List<ChangeInfo> info = res.get(n);
       if (results.get(n).more() && !info.isEmpty()) {
diff --git a/java/com/google/gerrit/server/restapi/change/RevertSubmission.java b/java/com/google/gerrit/server/restapi/change/RevertSubmission.java
index 7ba9b98..47c6a7e 100644
--- a/java/com/google/gerrit/server/restapi/change/RevertSubmission.java
+++ b/java/com/google/gerrit/server/restapi/change/RevertSubmission.java
@@ -160,7 +160,7 @@
             "Revert this change and all changes that have been submitted together with this change")
         .setVisible(
             and(
-                change.isMerged() && projectStatePermitsWrite,
+                change.isMerged() && change.getSubmissionId() != null && projectStatePermitsWrite,
                 permissionBackend
                     .user(rsrc.getUser())
                     .ref(change.getDest())
diff --git a/java/com/google/gerrit/server/restapi/group/GroupsCollection.java b/java/com/google/gerrit/server/restapi/group/GroupsCollection.java
index 52fe9d0..b92a464 100644
--- a/java/com/google/gerrit/server/restapi/group/GroupsCollection.java
+++ b/java/com/google/gerrit/server/restapi/group/GroupsCollection.java
@@ -14,13 +14,10 @@
 
 package com.google.gerrit.server.restapi.group;
 
-import com.google.common.collect.ListMultimap;
 import com.google.gerrit.common.data.GroupDescription;
 import com.google.gerrit.extensions.registration.DynamicMap;
 import com.google.gerrit.extensions.restapi.AuthException;
-import com.google.gerrit.extensions.restapi.BadRequestException;
 import com.google.gerrit.extensions.restapi.IdString;
-import com.google.gerrit.extensions.restapi.NeedsParams;
 import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
 import com.google.gerrit.extensions.restapi.RestCollection;
 import com.google.gerrit.extensions.restapi.RestView;
@@ -33,27 +30,21 @@
 import com.google.inject.Inject;
 import com.google.inject.Provider;
 
-public class GroupsCollection
-    implements RestCollection<TopLevelResource, GroupResource>, NeedsParams {
+public class GroupsCollection implements RestCollection<TopLevelResource, GroupResource> {
   private final DynamicMap<RestView<GroupResource>> views;
-  private final Provider<ListGroups> list;
   private final Provider<QueryGroups> queryGroups;
   private final GroupControl.Factory groupControlFactory;
   private final GroupResolver groupResolver;
   private final Provider<CurrentUser> self;
 
-  private boolean hasQuery2;
-
   @Inject
   public GroupsCollection(
       DynamicMap<RestView<GroupResource>> views,
-      Provider<ListGroups> list,
       Provider<QueryGroups> queryGroups,
       GroupControl.Factory groupControlFactory,
       GroupResolver groupResolver,
       Provider<CurrentUser> self) {
     this.views = views;
-    this.list = list;
     this.queryGroups = queryGroups;
     this.groupControlFactory = groupControlFactory;
     this.groupResolver = groupResolver;
@@ -61,16 +52,6 @@
   }
 
   @Override
-  public void setParams(ListMultimap<String, String> params) throws BadRequestException {
-    if (params.containsKey("query") && params.containsKey("query2")) {
-      throw new BadRequestException("\"query\" and \"query2\" options are mutually exclusive");
-    }
-
-    // The --query2 option is defined in QueryGroups
-    this.hasQuery2 = params.containsKey("query2");
-  }
-
-  @Override
   public RestView<TopLevelResource> list() throws ResourceNotFoundException, AuthException {
     final CurrentUser user = self.get();
     if (user instanceof AnonymousUser) {
@@ -78,12 +59,7 @@
     } else if (!(user.isIdentifiedUser())) {
       throw new ResourceNotFoundException();
     }
-
-    if (hasQuery2) {
-      return queryGroups.get();
-    }
-
-    return list.get();
+    return queryGroups.get();
   }
 
   @Override
diff --git a/java/com/google/gerrit/server/restapi/group/ListGroups.java b/java/com/google/gerrit/server/restapi/group/ListGroups.java
index 899ed00..adc251c 100644
--- a/java/com/google/gerrit/server/restapi/group/ListGroups.java
+++ b/java/com/google/gerrit/server/restapi/group/ListGroups.java
@@ -129,21 +129,6 @@
     this.owned = owned;
   }
 
-  /**
-   * Add a group to inspect.
-   *
-   * @param uuid UUID of the group
-   * @deprecated use {@link #addGroup(AccountGroup.UUID)}.
-   */
-  @Deprecated
-  @Option(
-      name = "--query",
-      aliases = {"-q"},
-      usage = "group to inspect (deprecated: use --group/-g instead)")
-  void addGroup_Deprecated(AccountGroup.UUID uuid) {
-    addGroup(uuid);
-  }
-
   @Option(
       name = "--group",
       aliases = {"-g"},
diff --git a/java/com/google/gerrit/server/restapi/group/QueryGroups.java b/java/com/google/gerrit/server/restapi/group/QueryGroups.java
index 816fcf6..380d42e 100644
--- a/java/com/google/gerrit/server/restapi/group/QueryGroups.java
+++ b/java/com/google/gerrit/server/restapi/group/QueryGroups.java
@@ -32,6 +32,7 @@
 import com.google.gerrit.server.query.group.GroupQueryBuilder;
 import com.google.gerrit.server.query.group.GroupQueryProcessor;
 import com.google.inject.Inject;
+import com.google.inject.Provider;
 import java.util.ArrayList;
 import java.util.EnumSet;
 import java.util.List;
@@ -39,7 +40,7 @@
 
 public class QueryGroups implements RestReadView<TopLevelResource> {
   private final GroupQueryBuilder queryBuilder;
-  private final GroupQueryProcessor queryProcessor;
+  private final Provider<GroupQueryProcessor> queryProcessorProvider;
   private final GroupJson json;
 
   private String query;
@@ -47,12 +48,9 @@
   private int start;
   private EnumSet<ListGroupsOption> options = EnumSet.noneOf(ListGroupsOption.class);
 
-  // TODO(ekempin): --query in ListGroups is marked as deprecated, once it is
-  // removed we want to rename --query2 to --query here.
-  /** --query (-q) is already used by {@link ListGroups} */
   @Option(
-      name = "--query2",
-      aliases = {"-q2"},
+      name = "--query",
+      aliases = {"-q"},
       usage = "group query")
   public void setQuery(String query) {
     this.query = query;
@@ -88,9 +86,11 @@
 
   @Inject
   protected QueryGroups(
-      GroupQueryBuilder queryBuilder, GroupQueryProcessor queryProcessor, GroupJson json) {
+      GroupQueryBuilder queryBuilder,
+      Provider<GroupQueryProcessor> queryProcessorProvider,
+      GroupJson json) {
     this.queryBuilder = queryBuilder;
-    this.queryProcessor = queryProcessor;
+    this.queryProcessorProvider = queryProcessorProvider;
     this.json = json;
   }
 
@@ -101,6 +101,8 @@
       throw new BadRequestException("missing query field");
     }
 
+    GroupQueryProcessor queryProcessor = queryProcessorProvider.get();
+
     if (queryProcessor.isDisabled()) {
       throw new MethodNotAllowedException("query disabled");
     }
diff --git a/java/com/google/gerrit/server/restapi/project/CreateLabel.java b/java/com/google/gerrit/server/restapi/project/CreateLabel.java
index 03b9452..5d51527 100644
--- a/java/com/google/gerrit/server/restapi/project/CreateLabel.java
+++ b/java/com/google/gerrit/server/restapi/project/CreateLabel.java
@@ -91,85 +91,7 @@
     try (MetaDataUpdate md = updateFactory.create(rsrc.getNameKey())) {
       ProjectConfig config = projectConfigFactory.read(md);
 
-      if (config.getLabelSections().containsKey(id.get())) {
-        throw new ResourceConflictException(String.format("label %s already exists", id.get()));
-      }
-
-      for (String labelName : config.getLabelSections().keySet()) {
-        if (labelName.equalsIgnoreCase(id.get())) {
-          throw new ResourceConflictException(
-              String.format("label %s conflicts with existing label %s", id.get(), labelName));
-        }
-      }
-
-      if (input.values == null || input.values.isEmpty()) {
-        throw new BadRequestException("values are required");
-      }
-
-      List<LabelValue> values = LabelDefinitionInputParser.parseValues(input.values);
-
-      LabelType labelType;
-      try {
-        labelType = new LabelType(id.get(), values);
-      } catch (IllegalArgumentException e) {
-        throw new BadRequestException("invalid name: " + id.get(), e);
-      }
-
-      if (input.function != null && !input.function.trim().isEmpty()) {
-        labelType.setFunction(LabelDefinitionInputParser.parseFunction(input.function));
-      } else {
-        labelType.setFunction(LabelFunction.MAX_WITH_BLOCK);
-      }
-
-      if (input.defaultValue != null) {
-        labelType.setDefaultValue(
-            LabelDefinitionInputParser.parseDefaultValue(labelType, input.defaultValue));
-      }
-
-      if (input.branches != null) {
-        labelType.setRefPatterns(LabelDefinitionInputParser.parseBranches(input.branches));
-      }
-
-      if (input.canOverride != null) {
-        labelType.setCanOverride(input.canOverride);
-      }
-
-      if (input.copyAnyScore != null) {
-        labelType.setCopyAnyScore(input.copyAnyScore);
-      }
-
-      if (input.copyMinScore != null) {
-        labelType.setCopyMinScore(input.copyMinScore);
-      }
-
-      if (input.copyMaxScore != null) {
-        labelType.setCopyMaxScore(input.copyMaxScore);
-      }
-
-      if (input.copyAllScoresIfNoChange != null) {
-        labelType.setCopyAllScoresIfNoChange(input.copyAllScoresIfNoChange);
-      }
-
-      if (input.copyAllScoresIfNoCodeChange != null) {
-        labelType.setCopyAllScoresIfNoCodeChange(input.copyAllScoresIfNoCodeChange);
-      }
-
-      if (input.copyAllScoresOnTrivialRebase != null) {
-        labelType.setCopyAllScoresOnTrivialRebase(input.copyAllScoresOnTrivialRebase);
-      }
-
-      if (input.copyAllScoresOnMergeFirstParentUpdate != null) {
-        labelType.setCopyAllScoresOnMergeFirstParentUpdate(
-            input.copyAllScoresOnMergeFirstParentUpdate);
-      }
-
-      if (input.allowPostSubmit != null) {
-        labelType.setAllowPostSubmit(input.allowPostSubmit);
-      }
-
-      if (input.ignoreSelfApproval != null) {
-        labelType.setIgnoreSelfApproval(input.ignoreSelfApproval);
-      }
+      LabelType labelType = createLabel(config, id.get(), input);
 
       if (input.commitMessage != null) {
         md.setMessage(Strings.emptyToNull(input.commitMessage.trim()));
@@ -177,7 +99,6 @@
         md.setMessage("Update label");
       }
 
-      config.getLabelSections().put(labelType.getName(), labelType);
       config.commit(md);
 
       projectCache.evict(rsrc.getProjectState().getProject());
@@ -185,4 +106,101 @@
       return Response.created(LabelDefinitionJson.format(rsrc.getNameKey(), labelType));
     }
   }
+
+  /**
+   * Creates a new label.
+   *
+   * @param config the project config
+   * @param label the name of the new label
+   * @param input the input that describes the new label
+   * @return the created label type
+   * @throws BadRequestException if there was invalid data in the input
+   * @throws ResourceConflictException if the label cannot be created due to a conflict
+   */
+  public LabelType createLabel(ProjectConfig config, String label, LabelDefinitionInput input)
+      throws BadRequestException, ResourceConflictException {
+    if (config.getLabelSections().containsKey(label)) {
+      throw new ResourceConflictException(String.format("label %s already exists", label));
+    }
+
+    for (String labelName : config.getLabelSections().keySet()) {
+      if (labelName.equalsIgnoreCase(label)) {
+        throw new ResourceConflictException(
+            String.format("label %s conflicts with existing label %s", label, labelName));
+      }
+    }
+
+    if (input.values == null || input.values.isEmpty()) {
+      throw new BadRequestException("values are required");
+    }
+
+    List<LabelValue> values = LabelDefinitionInputParser.parseValues(input.values);
+
+    LabelType labelType;
+    try {
+      labelType = new LabelType(label, values);
+    } catch (IllegalArgumentException e) {
+      throw new BadRequestException("invalid name: " + label, e);
+    }
+
+    if (input.function != null && !input.function.trim().isEmpty()) {
+      labelType.setFunction(LabelDefinitionInputParser.parseFunction(input.function));
+    } else {
+      labelType.setFunction(LabelFunction.MAX_WITH_BLOCK);
+    }
+
+    if (input.defaultValue != null) {
+      labelType.setDefaultValue(
+          LabelDefinitionInputParser.parseDefaultValue(labelType, input.defaultValue));
+    }
+
+    if (input.branches != null) {
+      labelType.setRefPatterns(LabelDefinitionInputParser.parseBranches(input.branches));
+    }
+
+    if (input.canOverride != null) {
+      labelType.setCanOverride(input.canOverride);
+    }
+
+    if (input.copyAnyScore != null) {
+      labelType.setCopyAnyScore(input.copyAnyScore);
+    }
+
+    if (input.copyMinScore != null) {
+      labelType.setCopyMinScore(input.copyMinScore);
+    }
+
+    if (input.copyMaxScore != null) {
+      labelType.setCopyMaxScore(input.copyMaxScore);
+    }
+
+    if (input.copyAllScoresIfNoChange != null) {
+      labelType.setCopyAllScoresIfNoChange(input.copyAllScoresIfNoChange);
+    }
+
+    if (input.copyAllScoresIfNoCodeChange != null) {
+      labelType.setCopyAllScoresIfNoCodeChange(input.copyAllScoresIfNoCodeChange);
+    }
+
+    if (input.copyAllScoresOnTrivialRebase != null) {
+      labelType.setCopyAllScoresOnTrivialRebase(input.copyAllScoresOnTrivialRebase);
+    }
+
+    if (input.copyAllScoresOnMergeFirstParentUpdate != null) {
+      labelType.setCopyAllScoresOnMergeFirstParentUpdate(
+          input.copyAllScoresOnMergeFirstParentUpdate);
+    }
+
+    if (input.allowPostSubmit != null) {
+      labelType.setAllowPostSubmit(input.allowPostSubmit);
+    }
+
+    if (input.ignoreSelfApproval != null) {
+      labelType.setIgnoreSelfApproval(input.ignoreSelfApproval);
+    }
+
+    config.getLabelSections().put(labelType.getName(), labelType);
+
+    return labelType;
+  }
 }
diff --git a/java/com/google/gerrit/server/restapi/project/DeleteBranch.java b/java/com/google/gerrit/server/restapi/project/DeleteBranch.java
index 2a20283..6248a61 100644
--- a/java/com/google/gerrit/server/restapi/project/DeleteBranch.java
+++ b/java/com/google/gerrit/server/restapi/project/DeleteBranch.java
@@ -17,6 +17,7 @@
 import static com.google.gerrit.entities.RefNames.isConfigRef;
 import static org.eclipse.jgit.lib.Constants.R_HEADS;
 
+import com.google.gerrit.entities.RefNames;
 import com.google.gerrit.extensions.common.Input;
 import com.google.gerrit.extensions.restapi.MethodNotAllowedException;
 import com.google.gerrit.extensions.restapi.ResourceConflictException;
@@ -46,7 +47,9 @@
   @Override
   public Response<?> apply(BranchResource rsrc, Input input)
       throws RestApiException, IOException, PermissionBackendException {
-    if (isConfigRef(rsrc.getBranchKey().branch())) {
+    if (RefNames.HEAD.equals(rsrc.getBranchKey().branch())) {
+      throw new MethodNotAllowedException("not allowed to delete HEAD");
+    } else if (isConfigRef(rsrc.getBranchKey().branch())) {
       // Never allow to delete the meta config branch.
       throw new MethodNotAllowedException(
           "not allowed to delete branch " + rsrc.getBranchKey().branch());
diff --git a/java/com/google/gerrit/server/restapi/project/DeleteBranches.java b/java/com/google/gerrit/server/restapi/project/DeleteBranches.java
index d429655..ca5962e 100644
--- a/java/com/google/gerrit/server/restapi/project/DeleteBranches.java
+++ b/java/com/google/gerrit/server/restapi/project/DeleteBranches.java
@@ -17,8 +17,10 @@
 import static org.eclipse.jgit.lib.Constants.R_HEADS;
 
 import com.google.common.collect.ImmutableSet;
+import com.google.gerrit.entities.RefNames;
 import com.google.gerrit.extensions.api.projects.DeleteBranchesInput;
 import com.google.gerrit.extensions.restapi.BadRequestException;
+import com.google.gerrit.extensions.restapi.MethodNotAllowedException;
 import com.google.gerrit.extensions.restapi.Response;
 import com.google.gerrit.extensions.restapi.RestApiException;
 import com.google.gerrit.extensions.restapi.RestModifyView;
@@ -43,6 +45,14 @@
     if (input == null || input.branches == null || input.branches.isEmpty()) {
       throw new BadRequestException("branches must be specified");
     }
+
+    if (input.branches.contains(RefNames.HEAD)) {
+      throw new MethodNotAllowedException("not allowed to delete HEAD");
+    } else if (input.branches.stream().anyMatch(RefNames::isConfigRef)) {
+      // Never allow to delete the meta config branch.
+      throw new MethodNotAllowedException("not allowed to delete branch " + RefNames.REFS_CONFIG);
+    }
+
     deleteRef.deleteMultipleRefs(
         project.getProjectState(), ImmutableSet.copyOf(input.branches), R_HEADS);
     return Response.none();
diff --git a/java/com/google/gerrit/server/restapi/project/DeleteLabel.java b/java/com/google/gerrit/server/restapi/project/DeleteLabel.java
index 5464abf..531640c 100644
--- a/java/com/google/gerrit/server/restapi/project/DeleteLabel.java
+++ b/java/com/google/gerrit/server/restapi/project/DeleteLabel.java
@@ -77,12 +77,10 @@
     try (MetaDataUpdate md = updateFactory.create(rsrc.getProject().getNameKey())) {
       ProjectConfig config = projectConfigFactory.read(md);
 
-      if (!config.getLabelSections().containsKey(rsrc.getLabelType().getName())) {
+      if (!deleteLabel(config, rsrc.getLabelType().getName())) {
         throw new ResourceNotFoundException(IdString.fromDecoded(rsrc.getLabelType().getName()));
       }
 
-      config.getLabelSections().remove(rsrc.getLabelType().getName());
-
       if (input.commitMessage != null) {
         md.setMessage(Strings.emptyToNull(input.commitMessage.trim()));
       } else {
@@ -96,4 +94,20 @@
 
     return Response.none();
   }
+
+  /**
+   * Delete the given label from the given project config.
+   *
+   * @param config the project config from which the label should be deleted
+   * @param labelName the name of the label that should be deleted
+   * @return {@code true} if the label was deleted, {@code false} if the label was not found
+   */
+  public boolean deleteLabel(ProjectConfig config, String labelName) {
+    if (!config.getLabelSections().containsKey(labelName)) {
+      return false;
+    }
+
+    config.getLabelSections().remove(labelName);
+    return true;
+  }
 }
diff --git a/java/com/google/gerrit/server/restapi/project/Module.java b/java/com/google/gerrit/server/restapi/project/Module.java
index 2c76cbd..5b3ea30 100644
--- a/java/com/google/gerrit/server/restapi/project/Module.java
+++ b/java/com/google/gerrit/server/restapi/project/Module.java
@@ -72,6 +72,7 @@
     get(LABEL_KIND).to(GetLabel.class);
     put(LABEL_KIND).to(SetLabel.class);
     delete(LABEL_KIND).to(DeleteLabel.class);
+    postOnCollection(LABEL_KIND).to(PostLabels.class);
 
     get(PROJECT_KIND, "HEAD").to(GetHead.class);
     put(PROJECT_KIND, "HEAD").to(SetHead.class);
diff --git a/java/com/google/gerrit/server/restapi/project/PostLabels.java b/java/com/google/gerrit/server/restapi/project/PostLabels.java
new file mode 100644
index 0000000..8835359
--- /dev/null
+++ b/java/com/google/gerrit/server/restapi/project/PostLabels.java
@@ -0,0 +1,148 @@
+// 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.server.restapi.project;
+
+import com.google.common.base.Strings;
+import com.google.gerrit.common.data.LabelType;
+import com.google.gerrit.extensions.common.BatchLabelInput;
+import com.google.gerrit.extensions.common.LabelDefinitionInput;
+import com.google.gerrit.extensions.restapi.AuthException;
+import com.google.gerrit.extensions.restapi.BadRequestException;
+import com.google.gerrit.extensions.restapi.ResourceConflictException;
+import com.google.gerrit.extensions.restapi.Response;
+import com.google.gerrit.extensions.restapi.RestCollectionModifyView;
+import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
+import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.git.meta.MetaDataUpdate;
+import com.google.gerrit.server.permissions.PermissionBackend;
+import com.google.gerrit.server.permissions.PermissionBackendException;
+import com.google.gerrit.server.permissions.ProjectPermission;
+import com.google.gerrit.server.project.LabelResource;
+import com.google.gerrit.server.project.ProjectCache;
+import com.google.gerrit.server.project.ProjectConfig;
+import com.google.gerrit.server.project.ProjectResource;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.google.inject.Singleton;
+import java.io.IOException;
+import java.util.Map.Entry;
+import org.eclipse.jgit.errors.ConfigInvalidException;
+
+/** REST endpoint that allows to add, update and delete label definitions in a batch. */
+@Singleton
+public class PostLabels
+    implements RestCollectionModifyView<ProjectResource, LabelResource, BatchLabelInput> {
+  private final Provider<CurrentUser> user;
+  private final PermissionBackend permissionBackend;
+  private final MetaDataUpdate.User updateFactory;
+  private final ProjectConfig.Factory projectConfigFactory;
+  private final DeleteLabel deleteLabel;
+  private final CreateLabel createLabel;
+  private final SetLabel setLabel;
+  private final ProjectCache projectCache;
+
+  @Inject
+  public PostLabels(
+      Provider<CurrentUser> user,
+      PermissionBackend permissionBackend,
+      MetaDataUpdate.User updateFactory,
+      ProjectConfig.Factory projectConfigFactory,
+      DeleteLabel deleteLabel,
+      CreateLabel createLabel,
+      SetLabel setLabel,
+      ProjectCache projectCache) {
+    this.user = user;
+    this.permissionBackend = permissionBackend;
+    this.updateFactory = updateFactory;
+    this.projectConfigFactory = projectConfigFactory;
+    this.deleteLabel = deleteLabel;
+    this.createLabel = createLabel;
+    this.setLabel = setLabel;
+    this.projectCache = projectCache;
+  }
+
+  @Override
+  public Response<?> apply(ProjectResource rsrc, BatchLabelInput input)
+      throws AuthException, UnprocessableEntityException, PermissionBackendException, IOException,
+          ConfigInvalidException, BadRequestException, ResourceConflictException {
+    if (!user.get().isIdentifiedUser()) {
+      throw new AuthException("Authentication required");
+    }
+
+    permissionBackend
+        .currentUser()
+        .project(rsrc.getNameKey())
+        .check(ProjectPermission.WRITE_CONFIG);
+
+    if (input == null) {
+      input = new BatchLabelInput();
+    }
+
+    try (MetaDataUpdate md = updateFactory.create(rsrc.getNameKey())) {
+      boolean dirty = false;
+
+      ProjectConfig config = projectConfigFactory.read(md);
+
+      if (input.delete != null && !input.delete.isEmpty()) {
+        for (String labelName : input.delete) {
+          if (!deleteLabel.deleteLabel(config, labelName.trim())) {
+            throw new UnprocessableEntityException(String.format("label %s not found", labelName));
+          }
+        }
+        dirty = true;
+      }
+
+      if (input.create != null && !input.create.isEmpty()) {
+        for (LabelDefinitionInput labelInput : input.create) {
+          if (labelInput.name == null || labelInput.name.trim().isEmpty()) {
+            throw new BadRequestException("label name is required for new label");
+          }
+          if (labelInput.commitMessage != null) {
+            throw new BadRequestException("commit message on label definition input not supported");
+          }
+          createLabel.createLabel(config, labelInput.name.trim(), labelInput);
+        }
+        dirty = true;
+      }
+
+      if (input.update != null && !input.update.isEmpty()) {
+        for (Entry<String, LabelDefinitionInput> e : input.update.entrySet()) {
+          LabelType labelType = config.getLabelSections().get(e.getKey().trim());
+          if (labelType == null) {
+            throw new UnprocessableEntityException(String.format("label %s not found", e.getKey()));
+          }
+          if (e.getValue().commitMessage != null) {
+            throw new BadRequestException("commit message on label definition input not supported");
+          }
+          setLabel.updateLabel(config, labelType, e.getValue());
+        }
+        dirty = true;
+      }
+
+      if (input.commitMessage != null) {
+        md.setMessage(Strings.emptyToNull(input.commitMessage.trim()));
+      } else {
+        md.setMessage("Update labels");
+      }
+
+      if (dirty) {
+        config.commit(md);
+        projectCache.evict(rsrc.getProjectState().getProject());
+      }
+    }
+
+    return Response.ok("");
+  }
+}
diff --git a/java/com/google/gerrit/server/restapi/project/QueryProjects.java b/java/com/google/gerrit/server/restapi/project/QueryProjects.java
index 7066d9a..e4f7df5 100644
--- a/java/com/google/gerrit/server/restapi/project/QueryProjects.java
+++ b/java/com/google/gerrit/server/restapi/project/QueryProjects.java
@@ -31,6 +31,7 @@
 import com.google.gerrit.server.query.project.ProjectQueryBuilder;
 import com.google.gerrit.server.query.project.ProjectQueryProcessor;
 import com.google.inject.Inject;
+import com.google.inject.Provider;
 import java.util.ArrayList;
 import java.util.List;
 import org.kohsuke.args4j.Option;
@@ -38,7 +39,7 @@
 public class QueryProjects implements RestReadView<TopLevelResource> {
   private final ProjectIndexCollection indexes;
   private final ProjectQueryBuilder queryBuilder;
-  private final ProjectQueryProcessor queryProcessor;
+  private final Provider<ProjectQueryProcessor> queryProcessorProvider;
   private final ProjectJson json;
 
   private String query;
@@ -78,11 +79,11 @@
   protected QueryProjects(
       ProjectIndexCollection indexes,
       ProjectQueryBuilder queryBuilder,
-      ProjectQueryProcessor queryProcessor,
+      Provider<ProjectQueryProcessor> queryProcessorProvider,
       ProjectJson json) {
     this.indexes = indexes;
     this.queryBuilder = queryBuilder;
-    this.queryProcessor = queryProcessor;
+    this.queryProcessorProvider = queryProcessorProvider;
     this.json = json;
   }
 
@@ -102,6 +103,8 @@
       throw new MethodNotAllowedException("no project index");
     }
 
+    ProjectQueryProcessor queryProcessor = queryProcessorProvider.get();
+
     if (start != 0) {
       queryProcessor.setStart(start);
     }
diff --git a/java/com/google/gerrit/server/restapi/project/SetLabel.java b/java/com/google/gerrit/server/restapi/project/SetLabel.java
index b7cffce..824b4ed 100644
--- a/java/com/google/gerrit/server/restapi/project/SetLabel.java
+++ b/java/com/google/gerrit/server/restapi/project/SetLabel.java
@@ -80,117 +80,9 @@
     LabelType labelType = rsrc.getLabelType();
 
     try (MetaDataUpdate md = updateFactory.create(rsrc.getProject().getNameKey())) {
-      boolean dirty = false;
-
       ProjectConfig config = projectConfigFactory.read(md);
-      config.getLabelSections().remove(labelType.getName());
 
-      if (input.name != null) {
-        String newName = input.name.trim();
-        if (newName.isEmpty()) {
-          throw new BadRequestException("name cannot be empty");
-        }
-        if (!newName.equals(labelType.getName())) {
-          if (config.getLabelSections().containsKey(newName)) {
-            throw new ResourceConflictException(String.format("name %s already in use", newName));
-          }
-
-          for (String labelName : config.getLabelSections().keySet()) {
-            if (labelName.equalsIgnoreCase(newName)) {
-              throw new ResourceConflictException(
-                  String.format("name %s conflicts with existing label %s", newName, labelName));
-            }
-          }
-
-          try {
-            labelType.setName(newName);
-          } catch (IllegalArgumentException e) {
-            throw new BadRequestException("invalid name: " + input.name, e);
-          }
-          dirty = true;
-        }
-      }
-
-      if (input.function != null) {
-        if (input.function.trim().isEmpty()) {
-          throw new BadRequestException("function cannot be empty");
-        }
-        labelType.setFunction(LabelDefinitionInputParser.parseFunction(input.function));
-        dirty = true;
-      }
-
-      if (input.values != null) {
-        if (input.values.isEmpty()) {
-          throw new BadRequestException("values cannot be empty");
-        }
-        labelType.setValues(LabelDefinitionInputParser.parseValues(input.values));
-        dirty = true;
-      }
-
-      if (input.defaultValue != null) {
-        labelType.setDefaultValue(
-            LabelDefinitionInputParser.parseDefaultValue(labelType, input.defaultValue));
-        dirty = true;
-      }
-
-      if (input.branches != null) {
-        labelType.setRefPatterns(LabelDefinitionInputParser.parseBranches(input.branches));
-        dirty = true;
-      }
-
-      if (input.canOverride != null) {
-        labelType.setCanOverride(input.canOverride);
-        dirty = true;
-      }
-
-      if (input.copyAnyScore != null) {
-        labelType.setCopyAnyScore(input.copyAnyScore);
-        dirty = true;
-      }
-
-      if (input.copyMinScore != null) {
-        labelType.setCopyMinScore(input.copyMinScore);
-        dirty = true;
-      }
-
-      if (input.copyMaxScore != null) {
-        labelType.setCopyMaxScore(input.copyMaxScore);
-        dirty = true;
-      }
-
-      if (input.copyAllScoresIfNoChange != null) {
-        labelType.setCopyAllScoresIfNoChange(input.copyAllScoresIfNoChange);
-      }
-
-      if (input.copyAllScoresIfNoCodeChange != null) {
-        labelType.setCopyAllScoresIfNoCodeChange(input.copyAllScoresIfNoCodeChange);
-        dirty = true;
-      }
-
-      if (input.copyAllScoresOnTrivialRebase != null) {
-        labelType.setCopyAllScoresOnTrivialRebase(input.copyAllScoresOnTrivialRebase);
-        dirty = true;
-      }
-
-      if (input.copyAllScoresOnMergeFirstParentUpdate != null) {
-        labelType.setCopyAllScoresOnMergeFirstParentUpdate(
-            input.copyAllScoresOnMergeFirstParentUpdate);
-        dirty = true;
-      }
-
-      if (input.allowPostSubmit != null) {
-        labelType.setAllowPostSubmit(input.allowPostSubmit);
-        dirty = true;
-      }
-
-      if (input.ignoreSelfApproval != null) {
-        labelType.setIgnoreSelfApproval(input.ignoreSelfApproval);
-        dirty = true;
-      }
-
-      if (dirty) {
-        config.getLabelSections().put(labelType.getName(), labelType);
-
+      if (updateLabel(config, labelType, input)) {
         if (input.commitMessage != null) {
           md.setMessage(Strings.emptyToNull(input.commitMessage.trim()));
         } else {
@@ -203,4 +95,128 @@
     }
     return Response.ok(LabelDefinitionJson.format(rsrc.getProject().getNameKey(), labelType));
   }
+
+  /**
+   * Updates the given label.
+   *
+   * @param config the project config
+   * @param labelType the label type that should be updated
+   * @param input the input that describes the label update
+   * @return whether the label type was modified
+   * @throws BadRequestException if there was invalid data in the input
+   * @throws ResourceConflictException if the update cannot be applied due to a conflict
+   */
+  public boolean updateLabel(ProjectConfig config, LabelType labelType, LabelDefinitionInput input)
+      throws BadRequestException, ResourceConflictException {
+    boolean dirty = false;
+
+    config.getLabelSections().remove(labelType.getName());
+
+    if (input.name != null) {
+      String newName = input.name.trim();
+      if (newName.isEmpty()) {
+        throw new BadRequestException("name cannot be empty");
+      }
+      if (!newName.equals(labelType.getName())) {
+        if (config.getLabelSections().containsKey(newName)) {
+          throw new ResourceConflictException(String.format("name %s already in use", newName));
+        }
+
+        for (String labelName : config.getLabelSections().keySet()) {
+          if (labelName.equalsIgnoreCase(newName)) {
+            throw new ResourceConflictException(
+                String.format("name %s conflicts with existing label %s", newName, labelName));
+          }
+        }
+
+        try {
+          labelType.setName(newName);
+        } catch (IllegalArgumentException e) {
+          throw new BadRequestException("invalid name: " + input.name, e);
+        }
+        dirty = true;
+      }
+    }
+
+    if (input.function != null) {
+      if (input.function.trim().isEmpty()) {
+        throw new BadRequestException("function cannot be empty");
+      }
+      labelType.setFunction(LabelDefinitionInputParser.parseFunction(input.function));
+      dirty = true;
+    }
+
+    if (input.values != null) {
+      if (input.values.isEmpty()) {
+        throw new BadRequestException("values cannot be empty");
+      }
+      labelType.setValues(LabelDefinitionInputParser.parseValues(input.values));
+      dirty = true;
+    }
+
+    if (input.defaultValue != null) {
+      labelType.setDefaultValue(
+          LabelDefinitionInputParser.parseDefaultValue(labelType, input.defaultValue));
+      dirty = true;
+    }
+
+    if (input.branches != null) {
+      labelType.setRefPatterns(LabelDefinitionInputParser.parseBranches(input.branches));
+      dirty = true;
+    }
+
+    if (input.canOverride != null) {
+      labelType.setCanOverride(input.canOverride);
+      dirty = true;
+    }
+
+    if (input.copyAnyScore != null) {
+      labelType.setCopyAnyScore(input.copyAnyScore);
+      dirty = true;
+    }
+
+    if (input.copyMinScore != null) {
+      labelType.setCopyMinScore(input.copyMinScore);
+      dirty = true;
+    }
+
+    if (input.copyMaxScore != null) {
+      labelType.setCopyMaxScore(input.copyMaxScore);
+      dirty = true;
+    }
+
+    if (input.copyAllScoresIfNoChange != null) {
+      labelType.setCopyAllScoresIfNoChange(input.copyAllScoresIfNoChange);
+    }
+
+    if (input.copyAllScoresIfNoCodeChange != null) {
+      labelType.setCopyAllScoresIfNoCodeChange(input.copyAllScoresIfNoCodeChange);
+      dirty = true;
+    }
+
+    if (input.copyAllScoresOnTrivialRebase != null) {
+      labelType.setCopyAllScoresOnTrivialRebase(input.copyAllScoresOnTrivialRebase);
+      dirty = true;
+    }
+
+    if (input.copyAllScoresOnMergeFirstParentUpdate != null) {
+      labelType.setCopyAllScoresOnMergeFirstParentUpdate(
+          input.copyAllScoresOnMergeFirstParentUpdate);
+      dirty = true;
+    }
+
+    if (input.allowPostSubmit != null) {
+      labelType.setAllowPostSubmit(input.allowPostSubmit);
+      dirty = true;
+    }
+
+    if (input.ignoreSelfApproval != null) {
+      labelType.setIgnoreSelfApproval(input.ignoreSelfApproval);
+      dirty = true;
+    }
+
+    config.getLabelSections().put(labelType.getName(), labelType);
+
+    return dirty;
+  }
 }
diff --git a/java/com/google/gerrit/server/submit/MergeSuperSet.java b/java/com/google/gerrit/server/submit/MergeSuperSet.java
index c690e0b..bcebc7f 100644
--- a/java/com/google/gerrit/server/submit/MergeSuperSet.java
+++ b/java/com/google/gerrit/server/submit/MergeSuperSet.java
@@ -99,7 +99,11 @@
         closeOrm = true;
       }
       List<ChangeData> cds = queryProvider.get().byLegacyChangeId(change.getId());
-      checkState(cds.size() == 1, "Expected exactly one ChangeData, got " + cds.size());
+      checkState(
+          cds.size() == 1,
+          "Expected exactly one ChangeData for change ID %s, got %s",
+          change.getId(),
+          cds.size());
       ChangeData cd = Iterables.getFirst(cds, null);
 
       boolean visible = false;
diff --git a/java/com/google/gerrit/server/update/RetryHelper.java b/java/com/google/gerrit/server/update/RetryHelper.java
index 36a609e..f8cf4cf 100644
--- a/java/com/google/gerrit/server/update/RetryHelper.java
+++ b/java/com/google/gerrit/server/update/RetryHelper.java
@@ -288,7 +288,7 @@
           () -> changeAction.call(updateFactory),
           opts,
           t -> {
-            if (t instanceof UpdateException) {
+            if (t instanceof UpdateException || t instanceof StorageException) {
               t = t.getCause();
             }
             return t instanceof LockFailureException;
@@ -378,7 +378,10 @@
   }
 
   private String formatCause(Throwable t) {
-    if ((t instanceof UpdateException || t instanceof StorageException) && t.getCause() != null) {
+    while ((t instanceof UpdateException
+            || t instanceof StorageException
+            || t instanceof ExecutionException)
+        && t.getCause() != null) {
       t = t.getCause();
     }
 
diff --git a/javatests/com/google/gerrit/acceptance/api/change/ChangeIT.java b/javatests/com/google/gerrit/acceptance/api/change/ChangeIT.java
index 1a4faa2..7ecb07e 100644
--- a/javatests/com/google/gerrit/acceptance/api/change/ChangeIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/change/ChangeIT.java
@@ -3390,6 +3390,7 @@
 
     ChangeInfo change = gApi.changes().id(r.getChangeId()).get();
     assertThat(change.status).isEqualTo(ChangeStatus.MERGED);
+    assertThat(change.submissionId).isNotNull();
     assertThat(change.labels.keySet()).containsExactly("Code-Review");
     assertThat(change.permittedLabels.keySet()).containsExactly("Code-Review");
     assertPermitted(change, "Code-Review", 2);
@@ -3550,6 +3551,7 @@
 
     ChangeInfo change = gApi.changes().id(r.getChangeId()).get();
     assertThat(change.status).isEqualTo(ChangeStatus.MERGED);
+    assertThat(change.submissionId).isNotNull();
     assertThat(change.labels.keySet()).containsExactly("Code-Review", "Non-Author-Code-Review");
     assertThat(change.permittedLabels.keySet()).containsExactly("Code-Review");
     assertPermitted(change, "Code-Review", 0, 1, 2);
@@ -3565,6 +3567,7 @@
 
     ChangeInfo change = gApi.changes().id(r.getChangeId()).get();
     assertThat(change.status).isEqualTo(ChangeStatus.MERGED);
+    assertThat(change.submissionId).isNotNull();
     assertThat(change.labels.keySet()).containsExactly("Code-Review");
     assertPermitted(change, "Code-Review", 0, 1, 2);
   }
diff --git a/javatests/com/google/gerrit/acceptance/api/change/QueryChangeIT.java b/javatests/com/google/gerrit/acceptance/api/change/QueryChangeIT.java
index 2cae04a..b5d673c 100644
--- a/javatests/com/google/gerrit/acceptance/api/change/QueryChangeIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/change/QueryChangeIT.java
@@ -15,22 +15,32 @@
 package com.google.gerrit.acceptance.api.change;
 
 import static com.google.common.truth.Truth.assertThat;
+import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.block;
+import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS;
+import static java.util.stream.Collectors.toList;
+import static javax.servlet.http.HttpServletResponse.SC_OK;
 
 import com.google.common.collect.ImmutableList;
 import com.google.gerrit.acceptance.AbstractDaemonTest;
 import com.google.gerrit.acceptance.NoHttpd;
+import com.google.gerrit.acceptance.UseClockStep;
 import com.google.gerrit.acceptance.config.GerritConfig;
+import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
+import com.google.gerrit.common.data.Permission;
+import com.google.gerrit.entities.Project;
 import com.google.gerrit.extensions.common.ChangeInfo;
 import com.google.gerrit.extensions.restapi.TopLevelResource;
 import com.google.gerrit.server.restapi.change.QueryChanges;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
 import java.util.List;
+import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository;
+import org.eclipse.jgit.junit.TestRepository;
 import org.junit.Test;
 
 @NoHttpd
 public class QueryChangeIT extends AbstractDaemonTest {
-
+  @Inject private ProjectOperations projectOperations;
   @Inject private Provider<QueryChanges> queryChangesProvider;
 
   @Test
@@ -123,6 +133,68 @@
     assertThat(result.get(2).get(0)._number).isEqualTo(numericId2);
   }
 
+  @Test
+  @UseClockStep
+  @SuppressWarnings("unchecked")
+  public void withPagedResults() throws Exception {
+    // Create 4 visible changes.
+    createChange(testRepo).getChange().getId().get();
+    createChange(testRepo).getChange().getId().get();
+    int changeId3 = createChange(testRepo).getChange().getId().get();
+    int changeId4 = createChange(testRepo).getChange().getId().get();
+
+    // Create hidden project.
+    Project.NameKey hiddenProject = projectOperations.newProject().create();
+    projectOperations
+        .project(hiddenProject)
+        .forUpdate()
+        .add(block(Permission.READ).ref("refs/*").group(REGISTERED_USERS))
+        .update();
+    TestRepository<InMemoryRepository> hiddenRepo = cloneProject(hiddenProject, admin);
+
+    // Create 2 hidden changes.
+    createChange(hiddenRepo);
+    createChange(hiddenRepo);
+
+    // Create a change query that matches all changes (visible and hidden changes).
+    // The index returns the changes ordered by last updated timestamp:
+    // hiddenChange2, hiddenChange1, change4, change3, change2, change1
+    QueryChanges queryChanges = queryChangesProvider.get();
+    queryChanges.addQuery("branch:master");
+
+    // Set a limit on the query so that we need to paginate over the results from the index.
+    queryChanges.setLimit(2);
+
+    // Execute the query and verify the results.
+    // Since the limit is set to 2, at most 2 changes are returned to user, but the index query is
+    // executed with limit 3 (+1 so that we can populate the _more_changes field on the last
+    // result).
+    // This means the index query with limit 3 returns these changes:
+    // hiddenChange2, hiddenChange1, change4
+    // The 2 hidden changes are filtered out because they are not visible to the caller.
+    // This means we have only one matching result (change4) but the limit (3) is not exhausted
+    // yet. Hence the next page is loaded from the index (startIndex is 3 to skip the results
+    // that we already processed, limit is again 3). The results for the next page are:
+    // change3, change2, change1
+    // change2 and change1 are dropped because they are over the limit.
+    List<ChangeInfo> result =
+        (List<ChangeInfo>) queryChanges.apply(TopLevelResource.INSTANCE).value();
+    assertThat(result.stream().map(i -> i._number).collect(toList()))
+        .containsExactly(changeId3, changeId4);
+  }
+
+  @Test
+  public void usingOutOfRangeLabelValuesDoesNotCauseError() throws Exception {
+    for (String operator : ImmutableList.of("=", ">", ">=", "<", "<=")) {
+      QueryChanges queryChanges = queryChangesProvider.get();
+      queryChanges.addQuery("label:Code-Review" + operator + "10");
+      queryChanges.addQuery("label:Code-Review" + operator + "-10");
+      queryChanges.addQuery("Code-Review" + operator + "10");
+      queryChanges.addQuery("Code-Review" + operator + "-10");
+      assertThat(queryChanges.apply(TopLevelResource.INSTANCE).statusCode()).isEqualTo(SC_OK);
+    }
+  }
+
   private static void assertNoChangeHasMoreChangesSet(List<ChangeInfo> results) {
     for (ChangeInfo info : results) {
       assertThat(info._moreChanges).isNull();
diff --git a/javatests/com/google/gerrit/acceptance/rest/binding/ProjectsRestApiBindingsIT.java b/javatests/com/google/gerrit/acceptance/rest/binding/ProjectsRestApiBindingsIT.java
index b8ab752..55eeaf4 100644
--- a/javatests/com/google/gerrit/acceptance/rest/binding/ProjectsRestApiBindingsIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/binding/ProjectsRestApiBindingsIT.java
@@ -83,7 +83,8 @@
               .expectedResponseCode(SC_NOT_FOUND)
               .build(),
           RestCall.get("/projects/%s/dashboards"),
-          RestCall.put("/projects/%s/labels/new-label"));
+          RestCall.put("/projects/%s/labels/new-label"),
+          RestCall.post("/projects/%s/labels/"));
 
   /**
    * Child project REST endpoints to be tested, each URL contains placeholders for the parent
diff --git a/javatests/com/google/gerrit/acceptance/rest/group/GroupsIT.java b/javatests/com/google/gerrit/acceptance/rest/group/GroupsIT.java
deleted file mode 100644
index e153e561..0000000
--- a/javatests/com/google/gerrit/acceptance/rest/group/GroupsIT.java
+++ /dev/null
@@ -1,31 +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.acceptance.rest.group;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import com.google.gerrit.acceptance.AbstractDaemonTest;
-import com.google.gerrit.acceptance.RestResponse;
-import org.junit.Test;
-
-public class GroupsIT extends AbstractDaemonTest {
-  @Test
-  public void invalidQueryOptions() throws Exception {
-    RestResponse r = adminRestSession.put("/groups/?query=foo&query2=bar");
-    r.assertBadRequest();
-    assertThat(r.getEntityContent())
-        .isEqualTo("\"query\" and \"query2\" options are mutually exclusive");
-  }
-}
diff --git a/javatests/com/google/gerrit/acceptance/rest/project/DeleteBranchIT.java b/javatests/com/google/gerrit/acceptance/rest/project/DeleteBranchIT.java
index 4f1be30..5636014 100644
--- a/javatests/com/google/gerrit/acceptance/rest/project/DeleteBranchIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/project/DeleteBranchIT.java
@@ -33,6 +33,7 @@
 import com.google.gerrit.extensions.api.projects.BranchInput;
 import com.google.gerrit.extensions.restapi.AuthException;
 import com.google.gerrit.extensions.restapi.IdString;
+import com.google.gerrit.extensions.restapi.MethodNotAllowedException;
 import com.google.gerrit.extensions.restapi.ResourceConflictException;
 import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
 import com.google.inject.Inject;
@@ -172,6 +173,24 @@
     assertThat(thrown).hasMessageThat().contains("Not allowed to delete group branch.");
   }
 
+  @Test
+  public void cannotDeleteRefsMetaConfig() throws Exception {
+    MethodNotAllowedException thrown =
+        assertThrows(
+            MethodNotAllowedException.class,
+            () -> branch(BranchNameKey.create(allUsers, RefNames.REFS_CONFIG)).delete());
+    assertThat(thrown).hasMessageThat().contains("not allowed to delete branch refs/meta/config");
+  }
+
+  @Test
+  public void cannotDeleteHead() throws Exception {
+    MethodNotAllowedException thrown =
+        assertThrows(
+            MethodNotAllowedException.class,
+            () -> branch(BranchNameKey.create(allUsers, RefNames.HEAD)).delete());
+    assertThat(thrown).hasMessageThat().contains("not allowed to delete HEAD");
+  }
+
   private void blockForcePush() throws Exception {
     projectOperations
         .project(project)
diff --git a/javatests/com/google/gerrit/acceptance/rest/project/DeleteBranchesIT.java b/javatests/com/google/gerrit/acceptance/rest/project/DeleteBranchesIT.java
index 3cdeafd..ad90109 100644
--- a/javatests/com/google/gerrit/acceptance/rest/project/DeleteBranchesIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/project/DeleteBranchesIT.java
@@ -35,6 +35,7 @@
 import com.google.gerrit.extensions.api.projects.ProjectApi;
 import com.google.gerrit.extensions.restapi.AuthException;
 import com.google.gerrit.extensions.restapi.BadRequestException;
+import com.google.gerrit.extensions.restapi.MethodNotAllowedException;
 import com.google.gerrit.extensions.restapi.ResourceConflictException;
 import com.google.inject.Inject;
 import java.util.HashMap;
@@ -156,6 +157,26 @@
     assertThat(thrown).hasMessageThat().contains("branches must be specified");
   }
 
+  @Test
+  public void cannotDeleteRefsMetaConfig() throws Exception {
+    DeleteBranchesInput input = new DeleteBranchesInput();
+    input.branches = Lists.newArrayList();
+    input.branches.add(RefNames.REFS_CONFIG);
+    MethodNotAllowedException thrown =
+        assertThrows(MethodNotAllowedException.class, () -> project().deleteBranches(input));
+    assertThat(thrown).hasMessageThat().contains("not allowed to delete branch refs/meta/config");
+  }
+
+  @Test
+  public void cannotDeleteHead() throws Exception {
+    DeleteBranchesInput input = new DeleteBranchesInput();
+    input.branches = Lists.newArrayList();
+    input.branches.add(RefNames.HEAD);
+    MethodNotAllowedException thrown =
+        assertThrows(MethodNotAllowedException.class, () -> project().deleteBranches(input));
+    assertThat(thrown).hasMessageThat().contains("not allowed to delete HEAD");
+  }
+
   private String errorMessageForBranches(List<String> branches) {
     StringBuilder message = new StringBuilder();
     for (String branch : branches) {
diff --git a/javatests/com/google/gerrit/acceptance/rest/project/PostLabelsIT.java b/javatests/com/google/gerrit/acceptance/rest/project/PostLabelsIT.java
new file mode 100644
index 0000000..9e6b051
--- /dev/null
+++ b/javatests/com/google/gerrit/acceptance/rest/project/PostLabelsIT.java
@@ -0,0 +1,456 @@
+// 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.acceptance.rest.project;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.allow;
+import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS;
+import static com.google.gerrit.testing.GerritJUnit.assertThrows;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.gerrit.acceptance.AbstractDaemonTest;
+import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
+import com.google.gerrit.acceptance.testsuite.request.RequestScopeOperations;
+import com.google.gerrit.common.data.LabelFunction;
+import com.google.gerrit.common.data.Permission;
+import com.google.gerrit.entities.RefNames;
+import com.google.gerrit.extensions.common.BatchLabelInput;
+import com.google.gerrit.extensions.common.LabelDefinitionInfo;
+import com.google.gerrit.extensions.common.LabelDefinitionInput;
+import com.google.gerrit.extensions.restapi.AuthException;
+import com.google.gerrit.extensions.restapi.BadRequestException;
+import com.google.gerrit.extensions.restapi.ResourceConflictException;
+import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
+import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
+import com.google.gerrit.server.restapi.project.PostLabels;
+import com.google.inject.Inject;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.junit.Test;
+
+/** Tests for the {@link PostLabels} REST endpoint. */
+public class PostLabelsIT extends AbstractDaemonTest {
+  @Inject private RequestScopeOperations requestScopeOperations;
+  @Inject private ProjectOperations projectOperations;
+
+  @Test
+  public void anonymous() throws Exception {
+    requestScopeOperations.setApiUserAnonymous();
+    AuthException thrown =
+        assertThrows(
+            AuthException.class,
+            () -> gApi.projects().name(allProjects.get()).labels(new BatchLabelInput()));
+    assertThat(thrown).hasMessageThat().contains("Authentication required");
+  }
+
+  @Test
+  public void notAllowed() throws Exception {
+    projectOperations
+        .project(allProjects)
+        .forUpdate()
+        .add(allow(Permission.READ).ref(RefNames.REFS_CONFIG).group(REGISTERED_USERS))
+        .update();
+
+    requestScopeOperations.setApiUser(user.id());
+    AuthException thrown =
+        assertThrows(
+            AuthException.class,
+            () -> gApi.projects().name(allProjects.get()).labels(new BatchLabelInput()));
+    assertThat(thrown).hasMessageThat().contains("write refs/meta/config not permitted");
+  }
+
+  @Test
+  public void deleteNonExistingLabel() throws Exception {
+    BatchLabelInput input = new BatchLabelInput();
+    input.delete = ImmutableList.of("Foo");
+
+    UnprocessableEntityException thrown =
+        assertThrows(
+            UnprocessableEntityException.class,
+            () -> gApi.projects().name(allProjects.get()).labels(input));
+    assertThat(thrown).hasMessageThat().contains("label Foo not found");
+  }
+
+  @Test
+  public void deleteLabels() throws Exception {
+    configLabel("Foo", LabelFunction.NO_OP);
+    configLabel("Bar", LabelFunction.NO_OP);
+    assertThat(gApi.projects().name(project.get()).labels().get()).isNotEmpty();
+
+    BatchLabelInput input = new BatchLabelInput();
+    input.delete = ImmutableList.of("Foo", "Bar");
+    gApi.projects().name(project.get()).labels(input);
+    assertThat(gApi.projects().name(project.get()).labels().get()).isEmpty();
+  }
+
+  @Test
+  public void deleteLabels_labelNamesAreTrimmed() throws Exception {
+    configLabel("Foo", LabelFunction.NO_OP);
+    configLabel("Bar", LabelFunction.NO_OP);
+    assertThat(gApi.projects().name(project.get()).labels().get()).isNotEmpty();
+
+    BatchLabelInput input = new BatchLabelInput();
+    input.delete = ImmutableList.of(" Foo ", " Bar ");
+    gApi.projects().name(project.get()).labels(input);
+    assertThat(gApi.projects().name(project.get()).labels().get()).isEmpty();
+  }
+
+  @Test
+  public void cannotDeleteTheSameLabelTwice() throws Exception {
+    configLabel("Foo", LabelFunction.NO_OP);
+
+    BatchLabelInput input = new BatchLabelInput();
+    input.delete = ImmutableList.of("Foo", "Foo");
+
+    UnprocessableEntityException thrown =
+        assertThrows(
+            UnprocessableEntityException.class,
+            () -> gApi.projects().name(project.get()).labels(input));
+    assertThat(thrown).hasMessageThat().contains("label Foo not found");
+  }
+
+  @Test
+  public void cannotCreateLabelWithNameThatIsAlreadyInUse() throws Exception {
+    LabelDefinitionInput labelInput = new LabelDefinitionInput();
+    labelInput.name = "Code-Review";
+    BatchLabelInput input = new BatchLabelInput();
+    input.create = ImmutableList.of(labelInput);
+
+    ResourceConflictException thrown =
+        assertThrows(
+            ResourceConflictException.class,
+            () -> gApi.projects().name(allProjects.get()).labels(input));
+    assertThat(thrown).hasMessageThat().contains("label Code-Review already exists");
+  }
+
+  @Test
+  public void cannotCreateTwoLabelsWithTheSameName() throws Exception {
+    LabelDefinitionInput fooInput = new LabelDefinitionInput();
+    fooInput.name = "Foo";
+    fooInput.values = ImmutableMap.of("+1", "Looks Good", " 0", "Don't Know", "-1", "Looks Bad");
+
+    BatchLabelInput input = new BatchLabelInput();
+    input.create = ImmutableList.of(fooInput, fooInput);
+
+    ResourceConflictException thrown =
+        assertThrows(
+            ResourceConflictException.class,
+            () -> gApi.projects().name(project.get()).labels(input));
+    assertThat(thrown).hasMessageThat().contains("label Foo already exists");
+  }
+
+  @Test
+  public void cannotCreateTwoLabelsWithNamesThatAreTheSameAfterTrim() throws Exception {
+    LabelDefinitionInput foo1Input = new LabelDefinitionInput();
+    foo1Input.name = "Foo";
+    foo1Input.values = ImmutableMap.of("+1", "Looks Good", " 0", "Don't Know", "-1", "Looks Bad");
+
+    LabelDefinitionInput foo2Input = new LabelDefinitionInput();
+    foo2Input.name = " Foo ";
+    foo2Input.values = ImmutableMap.of("+1", "Looks Good", " 0", "Don't Know", "-1", "Looks Bad");
+
+    BatchLabelInput input = new BatchLabelInput();
+    input.create = ImmutableList.of(foo1Input, foo2Input);
+
+    ResourceConflictException thrown =
+        assertThrows(
+            ResourceConflictException.class,
+            () -> gApi.projects().name(project.get()).labels(input));
+    assertThat(thrown).hasMessageThat().contains("label Foo already exists");
+  }
+
+  @Test
+  public void cannotCreateTwoLabelsWithConflictingNames() throws Exception {
+    LabelDefinitionInput foo1Input = new LabelDefinitionInput();
+    foo1Input.name = "Foo";
+    foo1Input.values = ImmutableMap.of("+1", "Looks Good", " 0", "Don't Know", "-1", "Looks Bad");
+
+    LabelDefinitionInput foo2Input = new LabelDefinitionInput();
+    foo2Input.name = "foo";
+    foo2Input.values = ImmutableMap.of("+1", "Looks Good", " 0", "Don't Know", "-1", "Looks Bad");
+
+    BatchLabelInput input = new BatchLabelInput();
+    input.create = ImmutableList.of(foo1Input, foo2Input);
+
+    ResourceConflictException thrown =
+        assertThrows(
+            ResourceConflictException.class,
+            () -> gApi.projects().name(project.get()).labels(input));
+    assertThat(thrown).hasMessageThat().contains("label foo conflicts with existing label Foo");
+  }
+
+  @Test
+  public void createLabels() throws Exception {
+    LabelDefinitionInput fooInput = new LabelDefinitionInput();
+    fooInput.name = "Foo";
+    fooInput.values = ImmutableMap.of("+1", "Looks Good", " 0", "Don't Know", "-1", "Looks Bad");
+
+    LabelDefinitionInput barInput = new LabelDefinitionInput();
+    barInput.name = "Bar";
+    barInput.values = ImmutableMap.of("+1", "Looks Good", " 0", "Don't Know", "-1", "Looks Bad");
+
+    BatchLabelInput input = new BatchLabelInput();
+    input.create = ImmutableList.of(fooInput, barInput);
+
+    gApi.projects().name(allProjects.get()).labels(input);
+    assertThat(gApi.projects().name(allProjects.get()).label("Foo").get()).isNotNull();
+    assertThat(gApi.projects().name(allProjects.get()).label("Bar").get()).isNotNull();
+  }
+
+  @Test
+  public void createLabels_labelNamesAreTrimmed() throws Exception {
+    LabelDefinitionInput fooInput = new LabelDefinitionInput();
+    fooInput.name = " Foo ";
+    fooInput.values = ImmutableMap.of("+1", "Looks Good", " 0", "Don't Know", "-1", "Looks Bad");
+
+    LabelDefinitionInput barInput = new LabelDefinitionInput();
+    barInput.name = " Bar ";
+    barInput.values = ImmutableMap.of("+1", "Looks Good", " 0", "Don't Know", "-1", "Looks Bad");
+
+    BatchLabelInput input = new BatchLabelInput();
+    input.create = ImmutableList.of(fooInput, barInput);
+
+    gApi.projects().name(allProjects.get()).labels(input);
+    assertThat(gApi.projects().name(allProjects.get()).label("Foo").get()).isNotNull();
+    assertThat(gApi.projects().name(allProjects.get()).label("Bar").get()).isNotNull();
+  }
+
+  @Test
+  public void cannotCreateLabelWithoutName() throws Exception {
+    BatchLabelInput input = new BatchLabelInput();
+    input.create = ImmutableList.of(new LabelDefinitionInput());
+
+    BadRequestException thrown =
+        assertThrows(
+            BadRequestException.class, () -> gApi.projects().name(allProjects.get()).labels(input));
+    assertThat(thrown).hasMessageThat().contains("label name is required for new label");
+  }
+
+  @Test
+  public void cannotSetCommitMessageOnLabelDefinitionInputForCreate() throws Exception {
+    LabelDefinitionInput labelInput = new LabelDefinitionInput();
+    labelInput.name = "Foo";
+    labelInput.commitMessage = "Create Label Foo";
+
+    BatchLabelInput input = new BatchLabelInput();
+    input.create = ImmutableList.of(labelInput);
+
+    BadRequestException thrown =
+        assertThrows(
+            BadRequestException.class, () -> gApi.projects().name(allProjects.get()).labels(input));
+    assertThat(thrown)
+        .hasMessageThat()
+        .contains("commit message on label definition input not supported");
+  }
+
+  @Test
+  public void updateNonExistingLabel() throws Exception {
+    BatchLabelInput input = new BatchLabelInput();
+    input.update = ImmutableMap.of("Foo", new LabelDefinitionInput());
+
+    UnprocessableEntityException thrown =
+        assertThrows(
+            UnprocessableEntityException.class,
+            () -> gApi.projects().name(allProjects.get()).labels(input));
+    assertThat(thrown).hasMessageThat().contains("label Foo not found");
+  }
+
+  @Test
+  public void updateLabels() throws Exception {
+    configLabel("Foo", LabelFunction.NO_OP);
+    configLabel("Bar", LabelFunction.NO_OP);
+
+    LabelDefinitionInput fooUpdate = new LabelDefinitionInput();
+    fooUpdate.function = LabelFunction.MAX_WITH_BLOCK.getFunctionName();
+    LabelDefinitionInput barUpdate = new LabelDefinitionInput();
+    barUpdate.name = "Baz";
+
+    BatchLabelInput input = new BatchLabelInput();
+    input.update = ImmutableMap.of("Foo", fooUpdate, "Bar", barUpdate);
+
+    gApi.projects().name(project.get()).labels(input);
+
+    assertThat(gApi.projects().name(project.get()).label("Foo").get().function)
+        .isEqualTo(fooUpdate.function);
+    assertThat(gApi.projects().name(project.get()).label("Baz").get()).isNotNull();
+    assertThrows(
+        ResourceNotFoundException.class,
+        () -> gApi.projects().name(project.get()).label("Bar").get());
+  }
+
+  @Test
+  public void updateLabels_labelNamesAreTrimmed() throws Exception {
+    configLabel("Foo", LabelFunction.NO_OP);
+    configLabel("Bar", LabelFunction.NO_OP);
+
+    LabelDefinitionInput fooUpdate = new LabelDefinitionInput();
+    fooUpdate.function = LabelFunction.MAX_WITH_BLOCK.getFunctionName();
+    LabelDefinitionInput barUpdate = new LabelDefinitionInput();
+    barUpdate.name = "Baz";
+
+    BatchLabelInput input = new BatchLabelInput();
+    input.update = ImmutableMap.of(" Foo ", fooUpdate, " Bar ", barUpdate);
+
+    gApi.projects().name(project.get()).labels(input);
+
+    assertThat(gApi.projects().name(project.get()).label("Foo").get().function)
+        .isEqualTo(fooUpdate.function);
+    assertThat(gApi.projects().name(project.get()).label("Baz").get()).isNotNull();
+    assertThrows(
+        ResourceNotFoundException.class,
+        () -> gApi.projects().name(project.get()).label("Bar").get());
+  }
+
+  @Test
+  public void cannotSetCommitMessageOnLabelDefinitionInputForUpdate() throws Exception {
+    LabelDefinitionInput labelInput = new LabelDefinitionInput();
+    labelInput.commitMessage = "Update label";
+
+    BatchLabelInput input = new BatchLabelInput();
+    input.update = ImmutableMap.of("Code-Review", labelInput);
+
+    BadRequestException thrown =
+        assertThrows(
+            BadRequestException.class, () -> gApi.projects().name(allProjects.get()).labels(input));
+    assertThat(thrown)
+        .hasMessageThat()
+        .contains("commit message on label definition input not supported");
+  }
+
+  @Test
+  public void deleteAndRecreateLabel() throws Exception {
+    configLabel("Foo", LabelFunction.NO_OP);
+
+    LabelDefinitionInput fooInput = new LabelDefinitionInput();
+    fooInput.name = "Foo";
+    fooInput.function = LabelFunction.MAX_NO_BLOCK.getFunctionName();
+    fooInput.values = ImmutableMap.of("+1", "Looks Good", " 0", "Don't Know", "-1", "Looks Bad");
+
+    BatchLabelInput input = new BatchLabelInput();
+    input.delete = ImmutableList.of("Foo");
+    input.create = ImmutableList.of(fooInput);
+
+    gApi.projects().name(project.get()).labels(input);
+
+    LabelDefinitionInfo fooLabel = gApi.projects().name(project.get()).label("Foo").get();
+    assertThat(fooLabel.function).isEqualTo(fooInput.function);
+  }
+
+  @Test
+  public void deleteRecreateAndUpdateLabel() throws Exception {
+    configLabel("Foo", LabelFunction.NO_OP);
+
+    LabelDefinitionInput fooCreateInput = new LabelDefinitionInput();
+    fooCreateInput.name = "Foo";
+    fooCreateInput.function = LabelFunction.MAX_NO_BLOCK.getFunctionName();
+    fooCreateInput.values =
+        ImmutableMap.of("+1", "Looks Good", " 0", "Don't Know", "-1", "Looks Bad");
+
+    LabelDefinitionInput fooUpdateInput = new LabelDefinitionInput();
+    fooUpdateInput.function = LabelFunction.ANY_WITH_BLOCK.getFunctionName();
+
+    BatchLabelInput input = new BatchLabelInput();
+    input.delete = ImmutableList.of("Foo");
+    input.create = ImmutableList.of(fooCreateInput);
+    input.update = ImmutableMap.of("Foo", fooUpdateInput);
+
+    gApi.projects().name(project.get()).labels(input);
+
+    LabelDefinitionInfo fooLabel = gApi.projects().name(project.get()).label("Foo").get();
+    assertThat(fooLabel.function).isEqualTo(fooUpdateInput.function);
+  }
+
+  @Test
+  public void cannotDeleteAndUpdateLabel() throws Exception {
+    configLabel("Foo", LabelFunction.NO_OP);
+
+    LabelDefinitionInput fooInput = new LabelDefinitionInput();
+    fooInput.function = LabelFunction.MAX_NO_BLOCK.getFunctionName();
+
+    BatchLabelInput input = new BatchLabelInput();
+    input.delete = ImmutableList.of("Foo");
+    input.update = ImmutableMap.of("Foo", fooInput);
+
+    UnprocessableEntityException thrown =
+        assertThrows(
+            UnprocessableEntityException.class,
+            () -> gApi.projects().name(project.get()).labels(input));
+    assertThat(thrown).hasMessageThat().contains("label Foo not found");
+  }
+
+  @Test
+  public void createAndUpdateLabel() throws Exception {
+    LabelDefinitionInput fooCreateInput = new LabelDefinitionInput();
+    fooCreateInput.name = "Foo";
+    fooCreateInput.function = LabelFunction.MAX_NO_BLOCK.getFunctionName();
+    fooCreateInput.values =
+        ImmutableMap.of("+1", "Looks Good", " 0", "Don't Know", "-1", "Looks Bad");
+
+    LabelDefinitionInput fooUpdateInput = new LabelDefinitionInput();
+    fooUpdateInput.function = LabelFunction.ANY_WITH_BLOCK.getFunctionName();
+
+    BatchLabelInput input = new BatchLabelInput();
+    input.create = ImmutableList.of(fooCreateInput);
+    input.update = ImmutableMap.of("Foo", fooUpdateInput);
+
+    gApi.projects().name(project.get()).labels(input);
+
+    LabelDefinitionInfo fooLabel = gApi.projects().name(project.get()).label("Foo").get();
+    assertThat(fooLabel.function).isEqualTo(fooUpdateInput.function);
+  }
+
+  @Test
+  public void noOpUpdate() throws Exception {
+    RevCommit refsMetaConfigHead =
+        projectOperations.project(allProjects).getHead(RefNames.REFS_CONFIG);
+
+    gApi.projects().name(allProjects.get()).labels(new BatchLabelInput());
+
+    assertThat(projectOperations.project(allProjects).getHead(RefNames.REFS_CONFIG))
+        .isEqualTo(refsMetaConfigHead);
+  }
+
+  @Test
+  public void defaultCommitMessage() throws Exception {
+    BatchLabelInput input = new BatchLabelInput();
+    input.delete = ImmutableList.of("Code-Review");
+    gApi.projects().name(allProjects.get()).labels(input);
+    assertThat(
+            projectOperations.project(allProjects).getHead(RefNames.REFS_CONFIG).getShortMessage())
+        .isEqualTo("Update labels");
+  }
+
+  @Test
+  public void withCommitMessage() throws Exception {
+    BatchLabelInput input = new BatchLabelInput();
+    input.commitMessage = "Batch Update Labels";
+    input.delete = ImmutableList.of("Code-Review");
+    gApi.projects().name(allProjects.get()).labels(input);
+    assertThat(
+            projectOperations.project(allProjects).getHead(RefNames.REFS_CONFIG).getShortMessage())
+        .isEqualTo(input.commitMessage);
+  }
+
+  @Test
+  public void commitMessageIsTrimmed() throws Exception {
+    BatchLabelInput input = new BatchLabelInput();
+    input.commitMessage = " Batch Update Labels ";
+    input.delete = ImmutableList.of("Code-Review");
+    gApi.projects().name(allProjects.get()).labels(input);
+    assertThat(
+            projectOperations.project(allProjects).getHead(RefNames.REFS_CONFIG).getShortMessage())
+        .isEqualTo("Batch Update Labels");
+  }
+}
diff --git a/javatests/com/google/gerrit/acceptance/server/change/BUILD b/javatests/com/google/gerrit/acceptance/server/change/BUILD
index 4d1634d..500ab06 100644
--- a/javatests/com/google/gerrit/acceptance/server/change/BUILD
+++ b/javatests/com/google/gerrit/acceptance/server/change/BUILD
@@ -4,5 +4,8 @@
     srcs = glob(["*IT.java"]),
     group = "server_change",
     labels = ["server"],
-    deps = ["//java/com/google/gerrit/server/util/time"],
+    deps = [
+        "//java/com/google/gerrit/server/logging",
+        "//java/com/google/gerrit/server/util/time",
+    ],
 )
diff --git a/javatests/com/google/gerrit/acceptance/server/change/CommentsIT.java b/javatests/com/google/gerrit/acceptance/server/change/CommentsIT.java
index d85a07b..65f8aa0 100644
--- a/javatests/com/google/gerrit/acceptance/server/change/CommentsIT.java
+++ b/javatests/com/google/gerrit/acceptance/server/change/CommentsIT.java
@@ -29,9 +29,11 @@
 import com.google.gerrit.acceptance.AbstractDaemonTest;
 import com.google.gerrit.acceptance.NoHttpd;
 import com.google.gerrit.acceptance.PushOneCommit;
+import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
 import com.google.gerrit.acceptance.testsuite.request.RequestScopeOperations;
 import com.google.gerrit.entities.Change;
 import com.google.gerrit.entities.Patch;
+import com.google.gerrit.entities.RefNames;
 import com.google.gerrit.extensions.api.changes.DeleteCommentInput;
 import com.google.gerrit.extensions.api.changes.DraftInput;
 import com.google.gerrit.extensions.api.changes.ReviewInput;
@@ -59,7 +61,6 @@
 import java.sql.Timestamp;
 import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.Collection;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -69,6 +70,7 @@
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 import org.eclipse.jgit.lib.ObjectReader;
+import org.eclipse.jgit.lib.Ref;
 import org.eclipse.jgit.lib.Repository;
 import org.eclipse.jgit.notes.NoteMap;
 import org.eclipse.jgit.revwalk.RevCommit;
@@ -82,6 +84,7 @@
   @Inject private Provider<ChangesCollection> changes;
   @Inject private Provider<PostReview> postReview;
   @Inject private RequestScopeOperations requestScopeOperations;
+  @Inject ProjectOperations projectOperations;
 
   private final Integer[] lines = {0, 1};
 
@@ -315,6 +318,47 @@
   }
 
   @Test
+  public void postCommentsUnreachableData() throws Exception {
+    requestScopeOperations.setApiUser(admin.id());
+
+    String file = "file";
+    PushOneCommit push =
+        pushFactory.create(admin.newIdent(), testRepo, "first subject", file, "l1\nl2\n");
+
+    String dest = "refs/for/master";
+    PushOneCommit.Result r1 = push.to(dest);
+    r1.assertOkStatus();
+    String changeId = r1.getChangeId();
+    String revId = r1.getCommit().getName();
+
+    PushOneCommit.Result r2 = amendChange(r1.getChangeId());
+    r2.assertOkStatus();
+
+    String draftRefName = RefNames.refsDraftComments(r1.getChange().getId(), admin.id());
+
+    DraftInput draft = newDraft(file, Side.REVISION, 1, "comment");
+    addDraft(changeId, "1", draft);
+    ReviewInput reviewInput = new ReviewInput();
+    reviewInput.drafts = DraftHandling.PUBLISH;
+    reviewInput.message = "foo";
+    gApi.changes().id(r1.getChangeId()).revision(1).review(reviewInput);
+
+    addDraft(changeId, "2", newDraft(file, Side.REVISION, 2, "comment2"));
+    reviewInput = new ReviewInput();
+    reviewInput.drafts = DraftHandling.PUBLISH_ALL_REVISIONS;
+    reviewInput.message = "bar";
+    gApi.changes().id(r1.getChangeId()).revision(2).review(reviewInput);
+
+    Map<String, List<CommentInfo>> drafts = getDraftComments(changeId, revId);
+    assertThat(drafts.isEmpty()).isTrue();
+
+    try (Repository repo = repoManager.openRepository(allUsers)) {
+      Ref ref = repo.exactRef(draftRefName);
+      assertThat(ref).isNull();
+    }
+  }
+
+  @Test
   public void listComments() throws Exception {
     String file = "file";
     PushOneCommit push =
@@ -1002,20 +1046,6 @@
     assertThat(getChangeSortedComments(id.get())).hasSize(3);
   }
 
-  @Test
-  public void jsonCommentHasLegacyFormatFalse() throws Exception {
-    PushOneCommit.Result result = createChange();
-    Change.Id changeId = result.getChange().getId();
-    addComment(result.getChangeId(), "comment");
-
-    Collection<com.google.gerrit.entities.Comment> comments =
-        notesFactory.createChecked(project, changeId).getComments().values();
-    assertThat(comments).hasSize(1);
-    com.google.gerrit.entities.Comment comment = comments.iterator().next();
-    assertThat(comment.message).isEqualTo("comment");
-    assertThat(comment.legacyFormat).isFalse();
-  }
-
   private List<CommentInfo> getRevisionComments(String changeId, String revId) throws Exception {
     return getPublishedComments(changeId, revId).values().stream()
         .flatMap(List::stream)
diff --git a/javatests/com/google/gerrit/acceptance/server/change/ConsistencyCheckerIT.java b/javatests/com/google/gerrit/acceptance/server/change/ConsistencyCheckerIT.java
index 1e2d1ba..08719d3 100644
--- a/javatests/com/google/gerrit/acceptance/server/change/ConsistencyCheckerIT.java
+++ b/javatests/com/google/gerrit/acceptance/server/change/ConsistencyCheckerIT.java
@@ -39,6 +39,7 @@
 import com.google.gerrit.server.change.ConsistencyChecker;
 import com.google.gerrit.server.change.NotifyResolver;
 import com.google.gerrit.server.change.PatchSetInserter;
+import com.google.gerrit.server.logging.RequestId;
 import com.google.gerrit.server.notedb.ChangeNoteUtil;
 import com.google.gerrit.server.notedb.ChangeNotes;
 import com.google.gerrit.server.notedb.Sequences;
@@ -313,7 +314,8 @@
             @Override
             public boolean updateChange(ChangeContext ctx) {
               ctx.getChange().setStatus(Change.Status.MERGED);
-              ctx.getUpdate(ctx.getChange().currentPatchSetId()).fixStatus(Change.Status.MERGED);
+              ctx.getUpdate(ctx.getChange().currentPatchSetId())
+                  .fixStatusToMerged(new RequestId(ctx.getChange().getId().toString()));
               return true;
             }
           });
@@ -862,7 +864,8 @@
             @Override
             public boolean updateChange(ChangeContext ctx) {
               ctx.getChange().setStatus(Change.Status.MERGED);
-              ctx.getUpdate(ctx.getChange().currentPatchSetId()).fixStatus(Change.Status.MERGED);
+              ctx.getUpdate(ctx.getChange().currentPatchSetId())
+                  .fixStatusToMerged(new RequestId(ctx.getChange().getId().toString()));
               return true;
             }
           });
diff --git a/javatests/com/google/gerrit/acceptance/server/change/PatchListCacheIT.java b/javatests/com/google/gerrit/acceptance/server/change/PatchListCacheIT.java
index 7c78d61..b23f9a3 100644
--- a/javatests/com/google/gerrit/acceptance/server/change/PatchListCacheIT.java
+++ b/javatests/com/google/gerrit/acceptance/server/change/PatchListCacheIT.java
@@ -39,7 +39,9 @@
 import com.google.gerrit.server.patch.Text;
 import com.google.inject.Inject;
 import com.google.inject.name.Named;
+import java.lang.reflect.Field;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Optional;
@@ -66,6 +68,25 @@
   private Cache<PatchListKey, PatchList> abstractPatchListCache;
 
   @Test
+  public void ensureLegacyBackendIsUsedForFileCacheBackend() throws Exception {
+    Field fileCacheField = patchListCache.getClass().getDeclaredField("fileCache");
+    fileCacheField.setAccessible(true);
+    // Use the reflection to access "localCache" field that is only present in Guava backend.
+    assertThat(
+            Arrays.stream(fileCacheField.get(patchListCache).getClass().getDeclaredFields())
+                .anyMatch(f -> f.getName().equals("localCache")))
+        .isTrue();
+
+    // intraCache (and all other cache backends) should use Caffeine backend.
+    Field intraCacheField = patchListCache.getClass().getDeclaredField("intraCache");
+    intraCacheField.setAccessible(true);
+    assertThat(
+            Arrays.stream(intraCacheField.get(patchListCache).getClass().getDeclaredFields())
+                .noneMatch(f -> f.getName().equals("localCache")))
+        .isTrue();
+  }
+
+  @Test
   public void listPatchesAgainstBase() throws Exception {
     commitBuilder().add(FILE_D, "4").message(SUBJECT_1).create();
     pushHead(testRepo, "refs/heads/master", false);
diff --git a/javatests/com/google/gerrit/index/query/RangeUtilTest.java b/javatests/com/google/gerrit/index/query/RangeUtilTest.java
new file mode 100644
index 0000000..681f9d99b
--- /dev/null
+++ b/javatests/com/google/gerrit/index/query/RangeUtilTest.java
@@ -0,0 +1,34 @@
+// 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.index.query;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.common.collect.ImmutableList;
+import com.google.gerrit.index.query.RangeUtil.Range;
+import org.junit.Test;
+
+public class RangeUtilTest {
+  @Test
+  public void getRangeForValueOutsideOfMinMaxRange_minNotGreaterThanMax() {
+    for (String operator : ImmutableList.of("=", ">", ">=", "<", "<=")) {
+      Range range = RangeUtil.getRange("foo", operator, 10, -4, 4);
+      assertThat(range.min).isAtMost(range.max);
+
+      range = RangeUtil.getRange("foo", operator, -10, -4, 4);
+      assertThat(range.min).isAtMost(range.max);
+    }
+  }
+}
diff --git a/javatests/com/google/gerrit/server/notedb/ChangeNotesStateTest.java b/javatests/com/google/gerrit/server/notedb/ChangeNotesStateTest.java
index 0d7f2bd..6ece894 100644
--- a/javatests/com/google/gerrit/server/notedb/ChangeNotesStateTest.java
+++ b/javatests/com/google/gerrit/server/notedb/ChangeNotesStateTest.java
@@ -938,7 +938,6 @@
                 .put("revId", String.class)
                 .put("serverId", String.class)
                 .put("unresolved", boolean.class)
-                .put("legacyFormat", boolean.class)
                 .build());
   }
 
diff --git a/lib/BUILD b/lib/BUILD
index 13580b1..39c622a 100644
--- a/lib/BUILD
+++ b/lib/BUILD
@@ -1,4 +1,4 @@
-load("@rules_java//java:defs.bzl", "java_library")
+load("@rules_java//java:defs.bzl", "java_import", "java_library")
 
 exports_files(glob([
     "LICENSE-*",
@@ -111,6 +111,29 @@
 )
 
 java_library(
+    name = "caffeine",
+    data = ["//lib:LICENSE-Apache2.0"],
+    visibility = [
+        "//java/com/google/gerrit/server/cache/mem:__pkg__",
+    ],
+    exports = ["@caffeine//jar"],
+)
+
+java_import(
+    name = "caffeine-guava-renamed",
+    jars = ["@caffeine-guava-renamed//file"],
+)
+
+java_library(
+    name = "caffeine-guava",
+    data = ["//lib:LICENSE-Apache2.0"],
+    visibility = [
+        "//java/com/google/gerrit/server/cache/mem:__pkg__",
+    ],
+    exports = [":caffeine-guava-renamed"],
+)
+
+java_library(
     name = "jsch",
     data = ["//lib:LICENSE-jsch"],
     visibility = ["//visibility:public"],
diff --git a/modules/jgit b/modules/jgit
index 63fc697..0356613 160000
--- a/modules/jgit
+++ b/modules/jgit
@@ -1 +1 @@
-Subproject commit 63fc6970cc51b712608d93e7cba0b85bb559ac52
+Subproject commit 0356613f48ebee2e3d2d65780e71d9e0b43a752e
diff --git a/polygerrit-ui/README.md b/polygerrit-ui/README.md
index 1e01759..461e2b9 100644
--- a/polygerrit-ui/README.md
+++ b/polygerrit-ui/README.md
@@ -184,6 +184,11 @@
 ```
 
 ## Template Type Safety
+
+> **Warning**: This feature is temporary disabled, because it doesn't work with Polymer 2 and Polymer 3. 
+Some of the checks are made by polymer linter.
+
+
 Polymer elements are not type checked against the element definition, making it
 trivial to break the display when refactoring or moving code. We now run
 additional tests to help ensure that template types are checked.
diff --git a/polygerrit-ui/app/BUILD b/polygerrit-ui/app/BUILD
index 1bf0f2a..5ff820b 100644
--- a/polygerrit-ui/app/BUILD
+++ b/polygerrit-ui/app/BUILD
@@ -125,6 +125,7 @@
     size = "large",
     srcs = ["polylint_test.sh"],
     data = [
+        "polymer.json",
         ":pg_code_without_test",
         "//polygerrit-ui:polygerrit_components.bower_components.zip",
     ],
diff --git a/polygerrit-ui/app/behaviors/async-foreach-behavior/async-foreach-behavior.html b/polygerrit-ui/app/behaviors/async-foreach-behavior/async-foreach-behavior.html
index 36e0201..f560ea8 100644
--- a/polygerrit-ui/app/behaviors/async-foreach-behavior/async-foreach-behavior.html
+++ b/polygerrit-ui/app/behaviors/async-foreach-behavior/async-foreach-behavior.html
@@ -45,5 +45,22 @@
       });
     },
   };
+
+  // eslint-disable-next-line no-unused-vars
+  function defineEmptyMixin() {
+    // This is a temporary function.
+    // Polymer linter doesn't process correctly the following code:
+    // class MyElement extends Polymer.mixinBehaviors([legacyBehaviors], ...) {...}
+    // To workaround this issue, the mock mixin is declared in this method.
+    // In the following changes, legacy behaviors will be converted to mixins.
+
+    /**
+     * @polymer
+     * @mixinFunction
+     */
+    Gerrit.AsyncForeachMixin = base =>
+      class extends base {
+      };
+  }
 })(window);
 </script>
diff --git a/polygerrit-ui/app/behaviors/async-foreach-behavior/async-foreach-behavior_test.html b/polygerrit-ui/app/behaviors/async-foreach-behavior/async-foreach-behavior_test.html
index 970bfc7..03ba6b1 100644
--- a/polygerrit-ui/app/behaviors/async-foreach-behavior/async-foreach-behavior_test.html
+++ b/polygerrit-ui/app/behaviors/async-foreach-behavior/async-foreach-behavior_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>async-foreach-behavior</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>
diff --git a/polygerrit-ui/app/behaviors/base-url-behavior/base-url-behavior.html b/polygerrit-ui/app/behaviors/base-url-behavior/base-url-behavior.html
index 1748647..92596e0 100644
--- a/polygerrit-ui/app/behaviors/base-url-behavior/base-url-behavior.html
+++ b/polygerrit-ui/app/behaviors/base-url-behavior/base-url-behavior.html
@@ -28,5 +28,22 @@
       return window.CANONICAL_PATH || '';
     },
   };
+
+  // eslint-disable-next-line no-unused-vars
+  function defineEmptyMixin() {
+    // This is a temporary function.
+    // Polymer linter doesn't process correctly the following code:
+    // class MyElement extends Polymer.mixinBehaviors([legacyBehaviors], ...) {...}
+    // To workaround this issue, the mock mixin is declared in this method.
+    // In the following changes, legacy behaviors will be converted to mixins.
+
+    /**
+     * @polymer
+     * @mixinFunction
+     */
+    Gerrit.BaseUrlMixin = base =>
+      class extends base {
+      };
+  }
 })(window);
 </script>
diff --git a/polygerrit-ui/app/behaviors/base-url-behavior/base-url-behavior_test.html b/polygerrit-ui/app/behaviors/base-url-behavior/base-url-behavior_test.html
index b61b142..5669bc4 100644
--- a/polygerrit-ui/app/behaviors/base-url-behavior/base-url-behavior_test.html
+++ b/polygerrit-ui/app/behaviors/base-url-behavior/base-url-behavior_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>base-url-behavior</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>
diff --git a/polygerrit-ui/app/behaviors/docs-url-behavior/docs-url-behavior.html b/polygerrit-ui/app/behaviors/docs-url-behavior/docs-url-behavior.html
index 4e25530..3361bc8 100644
--- a/polygerrit-ui/app/behaviors/docs-url-behavior/docs-url-behavior.html
+++ b/polygerrit-ui/app/behaviors/docs-url-behavior/docs-url-behavior.html
@@ -58,5 +58,22 @@
   },
   Gerrit.BaseUrlBehavior,
   ];
+
+  // eslint-disable-next-line no-unused-vars
+  function defineEmptyMixin() {
+    // This is a temporary function.
+    // Polymer linter doesn't process correctly the following code:
+    // class MyElement extends Polymer.mixinBehaviors([legacyBehaviors], ...) {...}
+    // To workaround this issue, the mock mixin is declared in this method.
+    // In the following changes, legacy behaviors will be converted to mixins.
+
+    /**
+     * @polymer
+     * @mixinFunction
+     */
+    Gerrit.DocsUrlMixin = base =>
+      class extends base {
+      };
+  }
 })(window);
 </script>
diff --git a/polygerrit-ui/app/behaviors/docs-url-behavior/docs-url-behavior_test.html b/polygerrit-ui/app/behaviors/docs-url-behavior/docs-url-behavior_test.html
index 2c513f3..e554012 100644
--- a/polygerrit-ui/app/behaviors/docs-url-behavior/docs-url-behavior_test.html
+++ b/polygerrit-ui/app/behaviors/docs-url-behavior/docs-url-behavior_test.html
@@ -19,7 +19,7 @@
 <script src="/bower_components/web-component-tester/browser.js"></script>
 <link rel="import" href="../../test/common-test-setup.html"/>
 <title>docs-url-behavior</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <link rel="import" href="docs-url-behavior.html">
diff --git a/polygerrit-ui/app/behaviors/dom-util-behavior/dom-util-behavior.html b/polygerrit-ui/app/behaviors/dom-util-behavior/dom-util-behavior.html
index 2d25b29..5daa032 100644
--- a/polygerrit-ui/app/behaviors/dom-util-behavior/dom-util-behavior.html
+++ b/polygerrit-ui/app/behaviors/dom-util-behavior/dom-util-behavior.html
@@ -42,5 +42,22 @@
       return isDescendant;
     },
   };
+
+  // eslint-disable-next-line no-unused-vars
+  function defineEmptyMixin() {
+    // This is a temporary function.
+    // Polymer linter doesn't process correctly the following code:
+    // class MyElement extends Polymer.mixinBehaviors([legacyBehaviors], ...) {...}
+    // To workaround this issue, the mock mixin is declared in this method.
+    // In the following changes, legacy behaviors will be converted to mixins.
+
+    /**
+     * @polymer
+     * @mixinFunction
+     */
+    Gerrit.DomUtilMixin = base =>
+      class extends base {
+      };
+  }
 })(window);
 </script>
diff --git a/polygerrit-ui/app/behaviors/dom-util-behavior/dom-util-behavior_test.html b/polygerrit-ui/app/behaviors/dom-util-behavior/dom-util-behavior_test.html
index 8323ac6..a52e0e2 100644
--- a/polygerrit-ui/app/behaviors/dom-util-behavior/dom-util-behavior_test.html
+++ b/polygerrit-ui/app/behaviors/dom-util-behavior/dom-util-behavior_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>dom-util-behavior</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>
diff --git a/polygerrit-ui/app/behaviors/fire-behavior/fire-behavior.html b/polygerrit-ui/app/behaviors/fire-behavior/fire-behavior.html
index b5afab1..5b3d420 100644
--- a/polygerrit-ui/app/behaviors/fire-behavior/fire-behavior.html
+++ b/polygerrit-ui/app/behaviors/fire-behavior/fire-behavior.html
@@ -51,5 +51,22 @@
       return event;
     },
   };
+
+  // eslint-disable-next-line no-unused-vars
+  function defineEmptyMixin() {
+    // This is a temporary function.
+    // Polymer linter doesn't process correctly the following code:
+    // class MyElement extends Polymer.mixinBehaviors([legacyBehaviors], ...) {...}
+    // To workaround this issue, the mock mixin is declared in this method.
+    // In the following changes, legacy behaviors will be converted to mixins.
+
+    /**
+     * @polymer
+     * @mixinFunction
+     */
+    Gerrit.FireMixin = base =>
+      class extends base {
+      };
+  }
 })(window);
 </script>
diff --git a/polygerrit-ui/app/behaviors/gr-access-behavior/gr-access-behavior.html b/polygerrit-ui/app/behaviors/gr-access-behavior/gr-access-behavior.html
index 0c75c44..9d3b1af 100644
--- a/polygerrit-ui/app/behaviors/gr-access-behavior/gr-access-behavior.html
+++ b/polygerrit-ui/app/behaviors/gr-access-behavior/gr-access-behavior.html
@@ -150,5 +150,29 @@
       });
     },
   };
+
+  // eslint-disable-next-line no-unused-vars
+  function defineEmptyMixin() {
+    // This is a temporary function.
+    // Polymer linter doesn't process correctly the following code:
+    // class MyElement extends Polymer.mixinBehaviors([legacyBehaviors], ...) {...}
+    // To workaround this issue, the mock mixin is declared in this method.
+    // In the following changes, legacy behaviors will be converted to mixins.
+
+    /**
+     * @polymer
+     * @mixinFunction
+     */
+    Gerrit.AccessMixin = base =>
+      class extends base {
+        static get properties() {
+          return {
+            permissionValues: {
+              type: Object,
+            },
+          };
+        }
+      };
+  }
 })(window);
 </script>
diff --git a/polygerrit-ui/app/behaviors/gr-access-behavior/gr-access-behavior_test.html b/polygerrit-ui/app/behaviors/gr-access-behavior/gr-access-behavior_test.html
index 0d1ee57..48565a6 100644
--- a/polygerrit-ui/app/behaviors/gr-access-behavior/gr-access-behavior_test.html
+++ b/polygerrit-ui/app/behaviors/gr-access-behavior/gr-access-behavior_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>keyboard-shortcut-behavior</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>
diff --git a/polygerrit-ui/app/behaviors/gr-admin-nav-behavior/gr-admin-nav-behavior.html b/polygerrit-ui/app/behaviors/gr-admin-nav-behavior/gr-admin-nav-behavior.html
index 182d242..07350b9 100644
--- a/polygerrit-ui/app/behaviors/gr-admin-nav-behavior/gr-admin-nav-behavior.html
+++ b/polygerrit-ui/app/behaviors/gr-admin-nav-behavior/gr-admin-nav-behavior.html
@@ -201,5 +201,22 @@
       };
     },
   };
+
+  // eslint-disable-next-line no-unused-vars
+  function defineEmptyMixin() {
+    // This is a temporary function.
+    // Polymer linter doesn't process correctly the following code:
+    // class MyElement extends Polymer.mixinBehaviors([legacyBehaviors], ...) {...}
+    // To workaround this issue, the mock mixin is declared in this method.
+    // In the following changes, legacy behaviors will be converted to mixins.
+
+    /**
+     * @polymer
+     * @mixinFunction
+     */
+    Gerrit.AdminNavMixin = base =>
+      class extends base {
+      };
+  }
 })(window);
 </script>
diff --git a/polygerrit-ui/app/behaviors/gr-admin-nav-behavior/gr-admin-nav-behavior_test.html b/polygerrit-ui/app/behaviors/gr-admin-nav-behavior/gr-admin-nav-behavior_test.html
index 90593a2..25b75c8 100644
--- a/polygerrit-ui/app/behaviors/gr-admin-nav-behavior/gr-admin-nav-behavior_test.html
+++ b/polygerrit-ui/app/behaviors/gr-admin-nav-behavior/gr-admin-nav-behavior_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>keyboard-shortcut-behavior</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>
diff --git a/polygerrit-ui/app/behaviors/gr-change-table-behavior/gr-change-table-behavior.html b/polygerrit-ui/app/behaviors/gr-change-table-behavior/gr-change-table-behavior.html
index c462c6f..db35a67 100644
--- a/polygerrit-ui/app/behaviors/gr-change-table-behavior/gr-change-table-behavior.html
+++ b/polygerrit-ui/app/behaviors/gr-change-table-behavior/gr-change-table-behavior.html
@@ -75,5 +75,31 @@
       return newColumns;
     },
   };
+
+  // eslint-disable-next-line no-unused-vars
+  function defineEmptyMixin() {
+    // This is a temporary function.
+    // Polymer linter doesn't process correctly the following code:
+    // class MyElement extends Polymer.mixinBehaviors([legacyBehaviors], ...) {...}
+    // To workaround this issue, the mock mixin is declared in this method.
+    // In the following changes, legacy behaviors will be converted to mixins.
+
+    /**
+     * @polymer
+     * @mixinFunction
+     */
+    Gerrit.ChangeTableMixin = base =>
+      class extends base {
+        static get properties() {
+          return {
+            columnNames: {
+              type: Array,
+            },
+          };
+        }
+
+        isColumnHidden(columnToCheck, columnsToDisplay) {}
+      };
+  }
 })(window);
 </script>
diff --git a/polygerrit-ui/app/behaviors/gr-change-table-behavior/gr-change-table-behavior_test.html b/polygerrit-ui/app/behaviors/gr-change-table-behavior/gr-change-table-behavior_test.html
index 791e2af..52931bc 100644
--- a/polygerrit-ui/app/behaviors/gr-change-table-behavior/gr-change-table-behavior_test.html
+++ b/polygerrit-ui/app/behaviors/gr-change-table-behavior/gr-change-table-behavior_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>keyboard-shortcut-behavior</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>
diff --git a/polygerrit-ui/app/behaviors/gr-display-name-behavior/gr-display-name-behavior.html b/polygerrit-ui/app/behaviors/gr-display-name-behavior/gr-display-name-behavior.html
index 3106fc8..e5ded0e 100644
--- a/polygerrit-ui/app/behaviors/gr-display-name-behavior/gr-display-name-behavior.html
+++ b/polygerrit-ui/app/behaviors/gr-display-name-behavior/gr-display-name-behavior.html
@@ -39,5 +39,22 @@
       return GrDisplayNameUtils.getGroupDisplayName(group);
     },
   };
+
+  // eslint-disable-next-line no-unused-vars
+  function defineEmptyMixin() {
+    // This is a temporary function.
+    // Polymer linter doesn't process correctly the following code:
+    // class MyElement extends Polymer.mixinBehaviors([legacyBehaviors], ...) {...}
+    // To workaround this issue, the mock mixin is declared in this method.
+    // In the following changes, legacy behaviors will be converted to mixins.
+
+    /**
+     * @polymer
+     * @mixinFunction
+     */
+    Gerrit.DisplayNameMixin = base =>
+      class extends base {
+      };
+  }
 })(window);
 </script>
diff --git a/polygerrit-ui/app/behaviors/gr-display-name-behavior/gr-display-name-behavior_test.html b/polygerrit-ui/app/behaviors/gr-display-name-behavior/gr-display-name-behavior_test.html
index 3d4eca1..aa217a1 100644
--- a/polygerrit-ui/app/behaviors/gr-display-name-behavior/gr-display-name-behavior_test.html
+++ b/polygerrit-ui/app/behaviors/gr-display-name-behavior/gr-display-name-behavior_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-display-name-behavior</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>
diff --git a/polygerrit-ui/app/behaviors/gr-list-view-behavior/gr-list-view-behavior.html b/polygerrit-ui/app/behaviors/gr-list-view-behavior/gr-list-view-behavior.html
index b6edb57..06912d5 100644
--- a/polygerrit-ui/app/behaviors/gr-list-view-behavior/gr-list-view-behavior.html
+++ b/polygerrit-ui/app/behaviors/gr-list-view-behavior/gr-list-view-behavior.html
@@ -59,5 +59,26 @@
   Gerrit.BaseUrlBehavior,
   Gerrit.URLEncodingBehavior,
   ];
+
+  // eslint-disable-next-line no-unused-vars
+  function defineEmptyMixin() {
+    // This is a temporary function.
+    // Polymer linter doesn't process correctly the following code:
+    // class MyElement extends Polymer.mixinBehaviors([legacyBehaviors], ...) {...}
+    // To workaround this issue, the mock mixin is declared in this method.
+    // In the following changes, legacy behaviors will be converted to mixins.
+
+    /**
+     * @polymer
+     * @mixinFunction
+     */
+    Gerrit.ListViewMixin = base =>
+      class extends base {
+        computeLoadingClass(loading) {}
+
+        computeShownItems(items) {}
+      };
+  }
 })(window);
+
 </script>
diff --git a/polygerrit-ui/app/behaviors/gr-list-view-behavior/gr-list-view-behavior_test.html b/polygerrit-ui/app/behaviors/gr-list-view-behavior/gr-list-view-behavior_test.html
index 535483d..9b48cdc 100644
--- a/polygerrit-ui/app/behaviors/gr-list-view-behavior/gr-list-view-behavior_test.html
+++ b/polygerrit-ui/app/behaviors/gr-list-view-behavior/gr-list-view-behavior_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>keyboard-shortcut-behavior</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>
diff --git a/polygerrit-ui/app/behaviors/gr-patch-set-behavior/gr-patch-set-behavior.html b/polygerrit-ui/app/behaviors/gr-patch-set-behavior/gr-patch-set-behavior.html
index 17b257a..c2ca081 100644
--- a/polygerrit-ui/app/behaviors/gr-patch-set-behavior/gr-patch-set-behavior.html
+++ b/polygerrit-ui/app/behaviors/gr-patch-set-behavior/gr-patch-set-behavior.html
@@ -272,5 +272,29 @@
       return -parseInt(rangeBase + '', 10);
     },
   };
+
+  // eslint-disable-next-line no-unused-vars
+  function defineEmptyMixin() {
+    // This is a temporary function.
+    // Polymer linter doesn't process correctly the following code:
+    // class MyElement extends Polymer.mixinBehaviors([legacyBehaviors], ...) {...}
+    // To workaround this issue, the mock mixin is declared in this method.
+    // In the following changes, legacy behaviors will be converted to mixins.
+
+    /**
+     * @polymer
+     * @mixinFunction
+     */
+    Gerrit.PatchSetMixin = base =>
+      class extends base {
+        computeLatestPatchNum(allPatchSets) {}
+
+        hasEditPatchsetLoaded(patchRangeRecord) {}
+
+        hasEditBasedOnCurrentPatchSet(allPatchSets) {}
+
+        computeAllPatchSets(change) {}
+      };
+  }
 })(window);
 </script>
diff --git a/polygerrit-ui/app/behaviors/gr-patch-set-behavior/gr-patch-set-behavior_test.html b/polygerrit-ui/app/behaviors/gr-patch-set-behavior/gr-patch-set-behavior_test.html
index 3db4084..5e70179 100644
--- a/polygerrit-ui/app/behaviors/gr-patch-set-behavior/gr-patch-set-behavior_test.html
+++ b/polygerrit-ui/app/behaviors/gr-patch-set-behavior/gr-patch-set-behavior_test.html
@@ -19,7 +19,7 @@
 <script src="/bower_components/web-component-tester/browser.js"></script>
 <link rel="import" href="../../test/common-test-setup.html"/>
 <title>gr-patch-set-behavior</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <link rel="import" href="gr-patch-set-behavior.html">
diff --git a/polygerrit-ui/app/behaviors/gr-path-list-behavior/gr-path-list-behavior.html b/polygerrit-ui/app/behaviors/gr-path-list-behavior/gr-path-list-behavior.html
index 5e597ae..8f9bf00 100644
--- a/polygerrit-ui/app/behaviors/gr-path-list-behavior/gr-path-list-behavior.html
+++ b/polygerrit-ui/app/behaviors/gr-path-list-behavior/gr-path-list-behavior.html
@@ -110,5 +110,25 @@
       return `\u2026/${pathPieces.slice(index).join('/')}`;
     },
   };
+
+  // eslint-disable-next-line no-unused-vars
+  function defineEmptyMixin() {
+    // This is a temporary function.
+    // Polymer linter doesn't process correctly the following code:
+    // class MyElement extends Polymer.mixinBehaviors([legacyBehaviors], ...) {...}
+    // To workaround this issue, the mock mixin is declared in this method.
+    // In the following changes, legacy behaviors will be converted to mixins.
+
+    /**
+     * @polymer
+     * @mixinFunction
+     */
+    Gerrit.PathListMixin = base =>
+      class extends base {
+        computeDisplayPath(path) {}
+
+        computeTruncatedPath(path) {}
+      };
+  }
 })(window);
 </script>
diff --git a/polygerrit-ui/app/behaviors/gr-path-list-behavior/gr-path-list-behavior_test.html b/polygerrit-ui/app/behaviors/gr-path-list-behavior/gr-path-list-behavior_test.html
index 0046290..924c98c 100644
--- a/polygerrit-ui/app/behaviors/gr-path-list-behavior/gr-path-list-behavior_test.html
+++ b/polygerrit-ui/app/behaviors/gr-path-list-behavior/gr-path-list-behavior_test.html
@@ -19,7 +19,7 @@
 <script src="/bower_components/web-component-tester/browser.js"></script>
 <link rel="import" href="../../test/common-test-setup.html"/>
 <title>gr-path-list-behavior</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <link rel="import" href="gr-path-list-behavior.html">
diff --git a/polygerrit-ui/app/behaviors/gr-repo-plugin-config-behavior/gr-repo-plugin-config-behavior.html b/polygerrit-ui/app/behaviors/gr-repo-plugin-config-behavior/gr-repo-plugin-config-behavior.html
index 2fa9191..69ebf23 100644
--- a/polygerrit-ui/app/behaviors/gr-repo-plugin-config-behavior/gr-repo-plugin-config-behavior.html
+++ b/polygerrit-ui/app/behaviors/gr-repo-plugin-config-behavior/gr-repo-plugin-config-behavior.html
@@ -34,5 +34,22 @@
     },
     PLUGIN_CONFIG_CHANGED: 'plugin-config-changed',
   };
+
+  // eslint-disable-next-line no-unused-vars
+  function defineEmptyMixin() {
+    // This is a temporary function.
+    // Polymer linter doesn't process correctly the following code:
+    // class MyElement extends Polymer.mixinBehaviors([legacyBehaviors], ...) {...}
+    // To workaround this issue, the mock mixin is declared in this method.
+    // In the following changes, legacy behaviors will be converted to mixins.
+
+    /**
+     * @polymer
+     * @mixinFunction
+     */
+    Gerrit.RepoPluginConfigMixin = base =>
+      class extends base {
+      };
+  }
 })(window);
 </script>
diff --git a/polygerrit-ui/app/behaviors/gr-tooltip-behavior/gr-tooltip-behavior.js b/polygerrit-ui/app/behaviors/gr-tooltip-behavior/gr-tooltip-behavior.js
index 0bf620f..73b8261 100644
--- a/polygerrit-ui/app/behaviors/gr-tooltip-behavior/gr-tooltip-behavior.js
+++ b/polygerrit-ui/app/behaviors/gr-tooltip-behavior/gr-tooltip-behavior.js
@@ -146,4 +146,28 @@
       }
     },
   };
+
+  // eslint-disable-next-line no-unused-vars
+  function defineEmptyMixin() {
+    // This is a temporary function.
+    // Polymer linter doesn't process correctly the following code:
+    // class MyElement extends Polymer.mixinBehaviors([legacyBehaviors], ...) {...}
+    // To workaround this issue, the mock mixin is declared in this method.
+    // In the following changes, legacy behaviors will be converted to mixins.
+
+    /**
+     * @polymer
+     * @mixinFunction
+     */
+    Gerrit.TooltipMixin = base =>
+      class extends base {
+        static get properties() {
+          return {
+            hasTooltip: {
+              type: Boolean,
+            },
+          };
+        }
+      };
+  }
 })(window);
diff --git a/polygerrit-ui/app/behaviors/gr-tooltip-behavior/gr-tooltip-behavior_test.html b/polygerrit-ui/app/behaviors/gr-tooltip-behavior/gr-tooltip-behavior_test.html
index 173c8d4..f3354a4 100644
--- a/polygerrit-ui/app/behaviors/gr-tooltip-behavior/gr-tooltip-behavior_test.html
+++ b/polygerrit-ui/app/behaviors/gr-tooltip-behavior/gr-tooltip-behavior_test.html
@@ -17,7 +17,7 @@
 -->
 
 <title>tooltip-behavior</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>
diff --git a/polygerrit-ui/app/behaviors/gr-url-encoding-behavior/gr-url-encoding-behavior.html b/polygerrit-ui/app/behaviors/gr-url-encoding-behavior/gr-url-encoding-behavior.html
index 69703f6..c4979b5 100644
--- a/polygerrit-ui/app/behaviors/gr-url-encoding-behavior/gr-url-encoding-behavior.html
+++ b/polygerrit-ui/app/behaviors/gr-url-encoding-behavior/gr-url-encoding-behavior.html
@@ -53,5 +53,22 @@
       return decodeURIComponent(withoutPlus);
     },
   };
+
+  // eslint-disable-next-line no-unused-vars
+  function defineEmptyMixin() {
+    // This is a temporary function.
+    // Polymer linter doesn't process correctly the following code:
+    // class MyElement extends Polymer.mixinBehaviors([legacyBehaviors], ...) {...}
+    // To workaround this issue, the mock mixin is declared in this method.
+    // In the following changes, legacy behaviors will be converted to mixins.
+
+    /**
+     * @polymer
+     * @mixinFunction
+     */
+    Gerrit.URLEncodingMixin = base =>
+      class extends base {
+      };
+  }
 })(window);
 </script>
diff --git a/polygerrit-ui/app/behaviors/gr-url-encoding-behavior/gr-url-encoding-behavior_test.html b/polygerrit-ui/app/behaviors/gr-url-encoding-behavior/gr-url-encoding-behavior_test.html
index 73e51d3..6cf2c68 100644
--- a/polygerrit-ui/app/behaviors/gr-url-encoding-behavior/gr-url-encoding-behavior_test.html
+++ b/polygerrit-ui/app/behaviors/gr-url-encoding-behavior/gr-url-encoding-behavior_test.html
@@ -17,7 +17,7 @@
 -->
 
 <title>gr-url-encoding-behavior</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>
diff --git a/polygerrit-ui/app/behaviors/keyboard-shortcut-behavior/keyboard-shortcut-behavior.html b/polygerrit-ui/app/behaviors/keyboard-shortcut-behavior/keyboard-shortcut-behavior.html
index 3c5a733..d4e2c72 100644
--- a/polygerrit-ui/app/behaviors/keyboard-shortcut-behavior/keyboard-shortcut-behavior.html
+++ b/polygerrit-ui/app/behaviors/keyboard-shortcut-behavior/keyboard-shortcut-behavior.html
@@ -612,5 +612,22 @@
       shortcutManager.bindShortcut(shortcut, ...bindings);
     },
   };
+
+  // eslint-disable-next-line no-unused-vars
+  function defineEmptyMixin() {
+    // This is a temporary function.
+    // Polymer linter doesn't process correctly the following code:
+    // class MyElement extends Polymer.mixinBehaviors([legacyBehaviors], ...) {...}
+    // To workaround this issue, the mock mixin is declared in this method.
+    // In the following changes, legacy behaviors will be converted to mixins.
+
+    /**
+     * @polymer
+     * @mixinFunction
+     */
+    Gerrit.KeyboardShortcutMixin = base =>
+      class extends base {
+      };
+  }
 })(window);
 </script>
diff --git a/polygerrit-ui/app/behaviors/keyboard-shortcut-behavior/keyboard-shortcut-behavior_test.html b/polygerrit-ui/app/behaviors/keyboard-shortcut-behavior/keyboard-shortcut-behavior_test.html
index 3183c7e..ba143ec 100644
--- a/polygerrit-ui/app/behaviors/keyboard-shortcut-behavior/keyboard-shortcut-behavior_test.html
+++ b/polygerrit-ui/app/behaviors/keyboard-shortcut-behavior/keyboard-shortcut-behavior_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>keyboard-shortcut-behavior</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>
diff --git a/polygerrit-ui/app/behaviors/rest-client-behavior/rest-client-behavior.html b/polygerrit-ui/app/behaviors/rest-client-behavior/rest-client-behavior.html
index fbeaa64..27041f7 100644
--- a/polygerrit-ui/app/behaviors/rest-client-behavior/rest-client-behavior.html
+++ b/polygerrit-ui/app/behaviors/rest-client-behavior/rest-client-behavior.html
@@ -177,5 +177,25 @@
   },
   Gerrit.BaseUrlBehavior,
   ];
+
+  // eslint-disable-next-line no-unused-vars
+  function defineEmptyMixin() {
+    // This is a temporary function.
+    // Polymer linter doesn't process correctly the following code:
+    // class MyElement extends Polymer.mixinBehaviors([legacyBehaviors], ...) {...}
+    // To workaround this issue, the mock mixin is declared in this method.
+    // In the following changes, legacy behaviors will be converted to mixins.
+
+    /**
+     * @polymer
+     * @mixinFunction
+     */
+    Gerrit.RESTClientMixin = base =>
+      class extends base {
+        changeStatusString(change) {}
+
+        changeStatuses(change, opt_options) {}
+      };
+  }
 })(window);
 </script>
diff --git a/polygerrit-ui/app/behaviors/rest-client-behavior/rest-client-behavior_test.html b/polygerrit-ui/app/behaviors/rest-client-behavior/rest-client-behavior_test.html
index a77a01f..cfcc11e 100644
--- a/polygerrit-ui/app/behaviors/rest-client-behavior/rest-client-behavior_test.html
+++ b/polygerrit-ui/app/behaviors/rest-client-behavior/rest-client-behavior_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>keyboard-shortcut-behavior</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>
diff --git a/polygerrit-ui/app/behaviors/safe-types-behavior/safe-types-behavior_test.html b/polygerrit-ui/app/behaviors/safe-types-behavior/safe-types-behavior_test.html
index ab446f1..7351647 100644
--- a/polygerrit-ui/app/behaviors/safe-types-behavior/safe-types-behavior_test.html
+++ b/polygerrit-ui/app/behaviors/safe-types-behavior/safe-types-behavior_test.html
@@ -17,7 +17,7 @@
 -->
 
 <title>safe-types-behavior</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>
diff --git a/polygerrit-ui/app/elements/admin/gr-access-section/gr-access-section.js b/polygerrit-ui/app/elements/admin/gr-access-section/gr-access-section.js
index afe3955..158e20b 100644
--- a/polygerrit-ui/app/elements/admin/gr-access-section/gr-access-section.js
+++ b/polygerrit-ui/app/elements/admin/gr-access-section/gr-access-section.js
@@ -36,61 +36,69 @@
   const ON_BEHALF_OF = '(On Behalf Of)';
   const LABEL = 'Label';
 
-  Polymer({
-    is: 'gr-access-section',
-
-    properties: {
-      capabilities: Object,
-      /** @type {?} */
-      section: {
-        type: Object,
-        notify: true,
-        observer: '_updateSection',
-      },
-      groups: Object,
-      labels: Object,
-      editing: {
-        type: Boolean,
-        value: false,
-        observer: '_handleEditingChanged',
-      },
-      canUpload: Boolean,
-      ownerOf: Array,
-      _originalId: String,
-      _editingRef: {
-        type: Boolean,
-        value: false,
-      },
-      _deleted: {
-        type: Boolean,
-        value: false,
-      },
-      _permissions: Array,
-    },
-
-    behaviors: [
-      Gerrit.AccessBehavior,
-      /**
+  /**
+    * @appliesMixin Gerrit.AccessMixin
+    * @appliesMixin Gerrit.FireMixin
+    */
+  class GrAccessSection extends Polymer.mixinBehaviors( [
+    Gerrit.AccessBehavior,
+    /**
        * Unused in this element, but called by other elements in tests
        * e.g gr-repo-access_test.
        */
-      Gerrit.FireBehavior,
-    ],
+    Gerrit.FireBehavior,
+  ], Polymer.GestureEventListeners(
+      Polymer.LegacyElementMixin(
+          Polymer.Element))) {
+    static get is() { return 'gr-access-section'; }
 
-    listeners: {
-      'access-saved': '_handleAccessSaved',
-    },
+    static get properties() {
+      return {
+        capabilities: Object,
+        /** @type {?} */
+        section: {
+          type: Object,
+          notify: true,
+          observer: '_updateSection',
+        },
+        groups: Object,
+        labels: Object,
+        editing: {
+          type: Boolean,
+          value: false,
+          observer: '_handleEditingChanged',
+        },
+        canUpload: Boolean,
+        ownerOf: Array,
+        _originalId: String,
+        _editingRef: {
+          type: Boolean,
+          value: false,
+        },
+        _deleted: {
+          type: Boolean,
+          value: false,
+        },
+        _permissions: Array,
+      };
+    }
+
+    created() {
+      super.created();
+      this.addEventListener('access-saved',
+          () => this._handleAccessSaved());
+    }
 
     _updateSection(section) {
       this._permissions = this.toSortedArray(section.value.permissions);
       this._originalId = section.id;
-    },
+    }
 
     _handleAccessSaved() {
       // Set a new 'original' value to keep track of after the value has been
       // saved.
       this._updateSection(this.section);
-    },
+    }
 
     _handleValueChange() {
       if (!this.section.value.added) {
@@ -103,7 +111,7 @@
             'access-modified', {bubbles: true, composed: true}));
       }
       this.section.value.updatedId = this.section.id;
-    },
+    }
 
     _handleEditingChanged(editing, editingOld) {
       // Ignore when editing gets set initially.
@@ -123,7 +131,7 @@
           }
         }
       }
-    },
+    }
 
     _computePermissions(name, capabilities, labels) {
       let allPermissions;
@@ -140,17 +148,17 @@
       return allPermissions.filter(permission => {
         return !this.section.value.permissions[permission.id];
       });
-    },
+    }
 
     _computeHideEditClass(section) {
       return section.id === 'GLOBAL_CAPABILITIES' ? 'hide' : '';
-    },
+    }
 
     _handleAddedPermissionRemoved(e) {
       const index = e.model.index;
       this._permissions = this._permissions.slice(0, index).concat(
           this._permissions.slice(index + 1, this._permissions.length));
-    },
+    }
 
     _computeLabelOptions(labels) {
       const labelOptions = [];
@@ -172,7 +180,7 @@
         });
       }
       return labelOptions;
-    },
+    }
 
     _computePermissionName(name, permission, permissionValues, capabilities) {
       if (name === GLOBAL_NAME) {
@@ -186,7 +194,7 @@
         }
         return `${LABEL} ${permission.value.label}${behalfOf}`;
       }
-    },
+    }
 
     _computeSectionName(name) {
       // When a new section is created, it doesn't yet have a ref. Set into
@@ -204,7 +212,7 @@
         return `Reference: ${name}`;
       }
       return name;
-    },
+    }
 
     _handleRemoveReference() {
       if (this.section.value.added) {
@@ -215,27 +223,27 @@
       this.section.value.deleted = true;
       this.dispatchEvent(
           new CustomEvent('access-modified', {bubbles: true, composed: true}));
-    },
+    }
 
     _handleUndoRemove() {
       this._deleted = false;
       delete this.section.value.deleted;
-    },
+    }
 
     editRefInput() {
       return Polymer.dom(this.root).querySelector(Polymer.Element ?
         'iron-input.editRefInput' :
         'input[is=iron-input].editRefInput');
-    },
+    }
 
     editReference() {
       this._editingRef = true;
       this.editRefInput().focus();
-    },
+    }
 
     _isEditEnabled(canUpload, ownerOf, sectionId) {
       return canUpload || (ownerOf && ownerOf.indexOf(sectionId) >= 0);
-    },
+    }
 
     _computeSectionClass(editing, canUpload, ownerOf, editingRef, deleted) {
       const classList = [];
@@ -249,11 +257,11 @@
         classList.push('deleted');
       }
       return classList.join(' ');
-    },
+    }
 
     _computeEditBtnClass(name) {
       return name === GLOBAL_NAME ? 'global' : '';
-    },
+    }
 
     _handleAddPermission() {
       const value = this.$.permissionSelect.value;
@@ -286,6 +294,8 @@
       this.push('_permissions', permission);
       this.set(['section.value.permissions', permission.id],
           permission.value);
-    },
-  });
+    }
+  }
+
+  customElements.define(GrAccessSection.is, GrAccessSection);
 })();
diff --git a/polygerrit-ui/app/elements/admin/gr-access-section/gr-access-section_test.html b/polygerrit-ui/app/elements/admin/gr-access-section/gr-access-section_test.html
index 16c654f..55b2495 100644
--- a/polygerrit-ui/app/elements/admin/gr-access-section/gr-access-section_test.html
+++ b/polygerrit-ui/app/elements/admin/gr-access-section/gr-access-section_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-access-section</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/page/page.js"></script>
diff --git a/polygerrit-ui/app/elements/admin/gr-admin-group-list/gr-admin-group-list.js b/polygerrit-ui/app/elements/admin/gr-admin-group-list/gr-admin-group-list.js
index 74dcb87..e387724 100644
--- a/polygerrit-ui/app/elements/admin/gr-admin-group-list/gr-admin-group-list.js
+++ b/polygerrit-ui/app/elements/admin/gr-admin-group-list/gr-admin-group-list.js
@@ -17,65 +17,72 @@
 (function() {
   'use strict';
 
-  Polymer({
-    is: 'gr-admin-group-list',
+  /**
+    * @appliesMixin Gerrit.FireMixin
+    * @appliesMixin Gerrit.ListViewMixin
+    */
+  class GrAdminGroupList extends Polymer.mixinBehaviors( [
+    Gerrit.FireBehavior,
+    Gerrit.ListViewBehavior,
+  ], Polymer.GestureEventListeners(
+      Polymer.LegacyElementMixin(
+          Polymer.Element))) {
+    static get is() { return 'gr-admin-group-list'; }
 
-    properties: {
+    static get properties() {
+      return {
       /**
        * URL params passed from the router.
        */
-      params: {
-        type: Object,
-        observer: '_paramsChanged',
-      },
+        params: {
+          type: Object,
+          observer: '_paramsChanged',
+        },
 
-      /**
+        /**
        * Offset of currently visible query results.
        */
-      _offset: Number,
-      _path: {
-        type: String,
-        readOnly: true,
-        value: '/admin/groups',
-      },
-      _hasNewGroupName: Boolean,
-      _createNewCapability: {
-        type: Boolean,
-        value: false,
-      },
-      _groups: Array,
+        _offset: Number,
+        _path: {
+          type: String,
+          readOnly: true,
+          value: '/admin/groups',
+        },
+        _hasNewGroupName: Boolean,
+        _createNewCapability: {
+          type: Boolean,
+          value: false,
+        },
+        _groups: Array,
 
-      /**
+        /**
        * Because  we request one more than the groupsPerPage, _shownGroups
        * may be one less than _groups.
        * */
-      _shownGroups: {
-        type: Array,
-        computed: 'computeShownItems(_groups)',
-      },
+        _shownGroups: {
+          type: Array,
+          computed: 'computeShownItems(_groups)',
+        },
 
-      _groupsPerPage: {
-        type: Number,
-        value: 25,
-      },
+        _groupsPerPage: {
+          type: Number,
+          value: 25,
+        },
 
-      _loading: {
-        type: Boolean,
-        value: true,
-      },
-      _filter: String,
-    },
-
-    behaviors: [
-      Gerrit.FireBehavior,
-      Gerrit.ListViewBehavior,
-    ],
+        _loading: {
+          type: Boolean,
+          value: true,
+        },
+        _filter: String,
+      };
+    }
 
     attached() {
+      super.attached();
       this._getCreateGroupCapability();
       this.fire('title-change', {title: 'Groups'});
       this._maybeOpenCreateOverlay(this.params);
-    },
+    }
 
     _paramsChanged(params) {
       this._loading = true;
@@ -84,7 +91,7 @@
 
       return this._getGroups(this._filter, this._groupsPerPage,
           this._offset);
-    },
+    }
 
     /**
      * Opens the create overlay if the route has a hash 'create'
@@ -94,11 +101,11 @@
       if (params && params.openCreateModal) {
         this.$.createOverlay.open();
       }
-    },
+    }
 
     _computeGroupUrl(id) {
       return Gerrit.Nav.getUrlForGroup(id);
-    },
+    }
 
     _getCreateGroupCapability() {
       return this.$.restAPI.getAccount().then(account => {
@@ -110,7 +117,7 @@
               }
             });
       });
-    },
+    }
 
     _getGroups(filter, groupsPerPage, offset) {
       this._groups = [];
@@ -127,30 +134,32 @@
                 });
             this._loading = false;
           });
-    },
+    }
 
     _refreshGroupsList() {
       this.$.restAPI.invalidateGroupsCache();
       return this._getGroups(this._filter, this._groupsPerPage,
           this._offset);
-    },
+    }
 
     _handleCreateGroup() {
       this.$.createNewModal.handleCreateGroup().then(() => {
         this._refreshGroupsList();
       });
-    },
+    }
 
     _handleCloseCreate() {
       this.$.createOverlay.close();
-    },
+    }
 
     _handleCreateClicked() {
       this.$.createOverlay.open();
-    },
+    }
 
     _visibleToAll(item) {
       return item.options.visible_to_all === true ? 'Y' : 'N';
-    },
-  });
+    }
+  }
+
+  customElements.define(GrAdminGroupList.is, GrAdminGroupList);
 })();
diff --git a/polygerrit-ui/app/elements/admin/gr-admin-group-list/gr-admin-group-list_test.html b/polygerrit-ui/app/elements/admin/gr-admin-group-list/gr-admin-group-list_test.html
index 58c7be4..455b3a2 100644
--- a/polygerrit-ui/app/elements/admin/gr-admin-group-list/gr-admin-group-list_test.html
+++ b/polygerrit-ui/app/elements/admin/gr-admin-group-list/gr-admin-group-list_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-admin-group-list</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/page/page.js"></script>
diff --git a/polygerrit-ui/app/elements/admin/gr-admin-view/gr-admin-view.js b/polygerrit-ui/app/elements/admin/gr-admin-view/gr-admin-view.js
index aa9cb00..23c7ae0 100644
--- a/polygerrit-ui/app/elements/admin/gr-admin-view/gr-admin-view.js
+++ b/polygerrit-ui/app/elements/admin/gr-admin-view/gr-admin-view.js
@@ -19,63 +19,73 @@
 
   const INTERNAL_GROUP_REGEX = /^[\da-f]{40}$/;
 
-  Polymer({
-    is: 'gr-admin-view',
+  /**
+    * @appliesMixin Gerrit.AdminNavMixin
+    * @appliesMixin Gerrit.BaseUrlMixin
+    * @appliesMixin Gerrit.URLEncodingMixin
+    */
+  class GrAdminView extends Polymer.mixinBehaviors( [
+    Gerrit.AdminNavBehavior,
+    Gerrit.BaseUrlBehavior,
+    Gerrit.URLEncodingBehavior,
+  ], Polymer.GestureEventListeners(
+      Polymer.LegacyElementMixin(
+          Polymer.Element))) {
+    static get is() { return 'gr-admin-view'; }
 
-    properties: {
+    static get properties() {
+      return {
       /** @type {?} */
-      params: Object,
-      path: String,
-      adminView: String,
+        params: Object,
+        path: String,
+        adminView: String,
 
-      _breadcrumbParentName: String,
-      _repoName: String,
-      _groupId: {
-        type: Number,
-        observer: '_computeGroupName',
-      },
-      _groupIsInternal: Boolean,
-      _groupName: String,
-      _groupOwner: {
-        type: Boolean,
-        value: false,
-      },
-      _subsectionLinks: Array,
-      _filteredLinks: Array,
-      _showDownload: {
-        type: Boolean,
-        value: false,
-      },
-      _isAdmin: {
-        type: Boolean,
-        value: false,
-      },
-      _showGroup: Boolean,
-      _showGroupAuditLog: Boolean,
-      _showGroupList: Boolean,
-      _showGroupMembers: Boolean,
-      _showRepoAccess: Boolean,
-      _showRepoCommands: Boolean,
-      _showRepoDashboards: Boolean,
-      _showRepoDetailList: Boolean,
-      _showRepoMain: Boolean,
-      _showRepoList: Boolean,
-      _showPluginList: Boolean,
-    },
+        _breadcrumbParentName: String,
+        _repoName: String,
+        _groupId: {
+          type: Number,
+          observer: '_computeGroupName',
+        },
+        _groupIsInternal: Boolean,
+        _groupName: String,
+        _groupOwner: {
+          type: Boolean,
+          value: false,
+        },
+        _subsectionLinks: Array,
+        _filteredLinks: Array,
+        _showDownload: {
+          type: Boolean,
+          value: false,
+        },
+        _isAdmin: {
+          type: Boolean,
+          value: false,
+        },
+        _showGroup: Boolean,
+        _showGroupAuditLog: Boolean,
+        _showGroupList: Boolean,
+        _showGroupMembers: Boolean,
+        _showRepoAccess: Boolean,
+        _showRepoCommands: Boolean,
+        _showRepoDashboards: Boolean,
+        _showRepoDetailList: Boolean,
+        _showRepoMain: Boolean,
+        _showRepoList: Boolean,
+        _showPluginList: Boolean,
+      };
+    }
 
-    behaviors: [
-      Gerrit.AdminNavBehavior,
-      Gerrit.BaseUrlBehavior,
-      Gerrit.URLEncodingBehavior,
-    ],
-
-    observers: [
-      '_paramsChanged(params)',
-    ],
+    static get observers() {
+      return [
+        '_paramsChanged(params)',
+      ];
+    }
 
     attached() {
+      super.attached();
       this.reload();
-    },
+    }
 
     reload() {
       const promises = [
@@ -123,18 +133,18 @@
                   });
             });
       });
-    },
+    }
 
     _computeSelectValue(params) {
       if (!params || !params.view) { return; }
       return params.view + (params.detail || '');
-    },
+    }
 
     _selectedIsCurrentPage(selected) {
       return (selected.parent === (this._repoName || this._groupId) &&
           selected.view === this.params.view &&
           selected.detailType === this.params.detail);
-    },
+    }
 
     _handleSubsectionChange(e) {
       const selected = this._subsectionLinks
@@ -145,7 +155,7 @@
         return;
       }
       Gerrit.Nav.navigateToRelativeUrl(selected.url);
-    },
+    }
 
     _paramsChanged(params) {
       const isGroupView = params.view === Gerrit.Nav.View.GROUP;
@@ -194,19 +204,19 @@
       }
       if (!needsReload) { return; }
       this.reload();
-    },
+    }
 
     // TODO (beckysiegel): Update these functions after router abstraction is
     // updated. They are currently copied from gr-dropdown (and should be
     // updated there as well once complete).
     _computeURLHelper(host, path) {
       return '//' + host + this.getBaseUrl() + path;
-    },
+    }
 
     _computeRelativeURL(path) {
       const host = window.location.host;
       return this._computeURLHelper(host, path);
-    },
+    }
 
     _computeLinkURL(link) {
       if (!link || typeof link.url === 'undefined') { return ''; }
@@ -214,7 +224,7 @@
         return link.url;
       }
       return this._computeRelativeURL(link.url);
-    },
+    }
 
     /**
      * @param {string} itemView
@@ -244,7 +254,7 @@
         return '';
       }
       return itemView === params.adminView ? 'selected' : '';
-    },
+    }
 
     _computeGroupName(groupId) {
       if (!groupId) { return ''; }
@@ -270,11 +280,13 @@
           this.reload();
         });
       });
-    },
+    }
 
     _updateGroupName(e) {
       this._groupName = e.detail.name;
       this.reload();
-    },
-  });
+    }
+  }
+
+  customElements.define(GrAdminView.is, GrAdminView);
 })();
diff --git a/polygerrit-ui/app/elements/admin/gr-admin-view/gr-admin-view_test.html b/polygerrit-ui/app/elements/admin/gr-admin-view/gr-admin-view_test.html
index 984be19..e6eef75 100644
--- a/polygerrit-ui/app/elements/admin/gr-admin-view/gr-admin-view_test.html
+++ b/polygerrit-ui/app/elements/admin/gr-admin-view/gr-admin-view_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-admin-view</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>
diff --git a/polygerrit-ui/app/elements/admin/gr-confirm-delete-item-dialog/gr-confirm-delete-item-dialog.js b/polygerrit-ui/app/elements/admin/gr-confirm-delete-item-dialog/gr-confirm-delete-item-dialog.js
index acc76de..e257c71 100644
--- a/polygerrit-ui/app/elements/admin/gr-confirm-delete-item-dialog/gr-confirm-delete-item-dialog.js
+++ b/polygerrit-ui/app/elements/admin/gr-confirm-delete-item-dialog/gr-confirm-delete-item-dialog.js
@@ -23,9 +23,15 @@
     TAGS: 'tags',
   };
 
-  Polymer({
-    is: 'gr-confirm-delete-item-dialog',
-
+  /**
+    * @appliesMixin Gerrit.FireMixin
+    */
+  class GrConfirmDeleteItemDialog extends Polymer.mixinBehaviors( [
+    Gerrit.FireBehavior,
+  ], Polymer.GestureEventListeners(
+      Polymer.LegacyElementMixin(
+          Polymer.Element))) {
+    static get is() { return 'gr-confirm-delete-item-dialog'; }
     /**
      * Fired when the confirm button is pressed.
      *
@@ -38,26 +44,24 @@
      * @event cancel
      */
 
-    properties: {
-      item: String,
-      itemType: String,
-    },
-
-    behaviors: [
-      Gerrit.FireBehavior,
-    ],
+    static get properties() {
+      return {
+        item: String,
+        itemType: String,
+      };
+    }
 
     _handleConfirmTap(e) {
       e.preventDefault();
       e.stopPropagation();
       this.fire('confirm', null, {bubbles: false});
-    },
+    }
 
     _handleCancelTap(e) {
       e.preventDefault();
       e.stopPropagation();
       this.fire('cancel', null, {bubbles: false});
-    },
+    }
 
     _computeItemName(detailType) {
       if (detailType === DETAIL_TYPES.BRANCHES) {
@@ -67,6 +71,9 @@
       } else if (detailType === DETAIL_TYPES.ID) {
         return 'ID';
       }
-    },
-  });
+    }
+  }
+
+  customElements.define(GrConfirmDeleteItemDialog.is,
+      GrConfirmDeleteItemDialog);
 })();
diff --git a/polygerrit-ui/app/elements/admin/gr-confirm-delete-item-dialog/gr-confirm-delete-item-dialog_test.html b/polygerrit-ui/app/elements/admin/gr-confirm-delete-item-dialog/gr-confirm-delete-item-dialog_test.html
index 3292cec..c83349b 100644
--- a/polygerrit-ui/app/elements/admin/gr-confirm-delete-item-dialog/gr-confirm-delete-item-dialog_test.html
+++ b/polygerrit-ui/app/elements/admin/gr-confirm-delete-item-dialog/gr-confirm-delete-item-dialog_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-confirm-delete-item-dialog</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>
diff --git a/polygerrit-ui/app/elements/admin/gr-create-change-dialog/gr-create-change-dialog.js b/polygerrit-ui/app/elements/admin/gr-create-change-dialog/gr-create-change-dialog.js
index e29e5f8..31e2793 100644
--- a/polygerrit-ui/app/elements/admin/gr-create-change-dialog/gr-create-change-dialog.js
+++ b/polygerrit-ui/app/elements/admin/gr-create-change-dialog/gr-create-change-dialog.js
@@ -20,44 +20,52 @@
   const SUGGESTIONS_LIMIT = 15;
   const REF_PREFIX = 'refs/heads/';
 
-  Polymer({
-    is: 'gr-create-change-dialog',
-
-    properties: {
-      repoName: String,
-      branch: String,
-      /** @type {?} */
-      _repoConfig: Object,
-      subject: String,
-      topic: String,
-      _query: {
-        type: Function,
-        value() {
-          return this._getRepoBranchesSuggestions.bind(this);
-        },
-      },
-      baseChange: String,
-      baseCommit: String,
-      privateByDefault: String,
-      canCreate: {
-        type: Boolean,
-        notify: true,
-        value: false,
-      },
-      _privateChangesEnabled: Boolean,
-    },
-
-    behaviors: [
-      Gerrit.BaseUrlBehavior,
-      /**
+  /**
+    * @appliesMixin Gerrit.BaseUrlMixin
+    * @appliesMixin Gerrit.FireMixin
+    * @appliesMixin Gerrit.URLEncodingMixin
+    */
+  class GrCreateChangeDialog extends Polymer.mixinBehaviors( [
+    Gerrit.BaseUrlBehavior,
+    /**
        * Unused in this element, but called by other elements in tests
        * e.g gr-repo-commands_test.
        */
-      Gerrit.FireBehavior,
-      Gerrit.URLEncodingBehavior,
-    ],
+    Gerrit.FireBehavior,
+    Gerrit.URLEncodingBehavior,
+  ], Polymer.GestureEventListeners(
+      Polymer.LegacyElementMixin(
+          Polymer.Element))) {
+    static get is() { return 'gr-create-change-dialog'; }
+
+    static get properties() {
+      return {
+        repoName: String,
+        branch: String,
+        /** @type {?} */
+        _repoConfig: Object,
+        subject: String,
+        topic: String,
+        _query: {
+          type: Function,
+          value() {
+            return this._getRepoBranchesSuggestions.bind(this);
+          },
+        },
+        baseChange: String,
+        baseCommit: String,
+        privateByDefault: String,
+        canCreate: {
+          type: Boolean,
+          notify: true,
+          value: false,
+        },
+        _privateChangesEnabled: Boolean,
+      };
+    }
 
     attached() {
+      super.attached();
       if (!this.repoName) { return Promise.resolve(); }
 
       const promises = [];
@@ -75,19 +83,21 @@
       }));
 
       return Promise.all(promises);
-    },
+    }
 
-    observers: [
-      '_allowCreate(branch, subject)',
-    ],
+    static get observers() {
+      return [
+        '_allowCreate(branch, subject)',
+      ];
+    }
 
     _computeBranchClass(baseChange) {
       return baseChange ? 'hide' : '';
-    },
+    }
 
     _allowCreate(branch, subject) {
       this.canCreate = !!branch && !!subject;
-    },
+    }
 
     handleCreateChange() {
       const isPrivate = this.$.privateChangeCheckBox.checked;
@@ -99,7 +109,7 @@
             if (!changeCreated) { return; }
             Gerrit.Nav.navigateToChange(changeCreated);
           });
-    },
+    }
 
     _getRepoBranchesSuggestions(input) {
       if (input.startsWith(REF_PREFIX)) {
@@ -122,7 +132,7 @@
         }
         return branches;
       });
-    },
+    }
 
     _formatBooleanString(config) {
       if (config && config.configured_value === 'TRUE') {
@@ -138,10 +148,12 @@
       } else {
         return false;
       }
-    },
+    }
 
     _computePrivateSectionClass(config) {
       return config ? 'hide' : '';
-    },
-  });
+    }
+  }
+
+  customElements.define(GrCreateChangeDialog.is, GrCreateChangeDialog);
 })();
diff --git a/polygerrit-ui/app/elements/admin/gr-create-change-dialog/gr-create-change-dialog_test.html b/polygerrit-ui/app/elements/admin/gr-create-change-dialog/gr-create-change-dialog_test.html
index 3a3683f..3367aa3 100644
--- a/polygerrit-ui/app/elements/admin/gr-create-change-dialog/gr-create-change-dialog_test.html
+++ b/polygerrit-ui/app/elements/admin/gr-create-change-dialog/gr-create-change-dialog_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-create-change-dialog</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>
diff --git a/polygerrit-ui/app/elements/admin/gr-create-group-dialog/gr-create-group-dialog.js b/polygerrit-ui/app/elements/admin/gr-create-group-dialog/gr-create-group-dialog.js
index 01aeb43..6b1af9a 100644
--- a/polygerrit-ui/app/elements/admin/gr-create-group-dialog/gr-create-group-dialog.js
+++ b/polygerrit-ui/app/elements/admin/gr-create-group-dialog/gr-create-group-dialog.js
@@ -17,40 +17,48 @@
 (function() {
   'use strict';
 
-  Polymer({
-    is: 'gr-create-group-dialog',
+  /**
+    * @appliesMixin Gerrit.BaseUrlMixin
+    * @appliesMixin Gerrit.URLEncodingMixin
+    */
+  class GrCreateGroupDialog extends Polymer.mixinBehaviors( [
+    Gerrit.BaseUrlBehavior,
+    Gerrit.URLEncodingBehavior,
+  ], Polymer.GestureEventListeners(
+      Polymer.LegacyElementMixin(
+          Polymer.Element))) {
+    static get is() { return 'gr-create-group-dialog'; }
 
-    properties: {
-      params: Object,
-      hasNewGroupName: {
-        type: Boolean,
-        notify: true,
-        value: false,
-      },
-      _name: Object,
-      _groupCreated: {
-        type: Boolean,
-        value: false,
-      },
-    },
+    static get properties() {
+      return {
+        params: Object,
+        hasNewGroupName: {
+          type: Boolean,
+          notify: true,
+          value: false,
+        },
+        _name: Object,
+        _groupCreated: {
+          type: Boolean,
+          value: false,
+        },
+      };
+    }
 
-    observers: [
-      '_updateGroupName(_name)',
-    ],
-
-    behaviors: [
-      Gerrit.BaseUrlBehavior,
-      Gerrit.URLEncodingBehavior,
-    ],
+    static get observers() {
+      return [
+        '_updateGroupName(_name)',
+      ];
+    }
 
     _computeGroupUrl(groupId) {
       return this.getBaseUrl() + '/admin/groups/' +
           this.encodeURL(groupId, true);
-    },
+    }
 
     _updateGroupName(name) {
       this.hasNewGroupName = !!name;
-    },
+    }
 
     handleCreateGroup() {
       return this.$.restAPI.createGroup({name: this._name})
@@ -62,6 +70,8 @@
                   page.show(this._computeGroupUrl(group.group_id));
                 });
           });
-    },
-  });
+    }
+  }
+
+  customElements.define(GrCreateGroupDialog.is, GrCreateGroupDialog);
 })();
diff --git a/polygerrit-ui/app/elements/admin/gr-create-group-dialog/gr-create-group-dialog_test.html b/polygerrit-ui/app/elements/admin/gr-create-group-dialog/gr-create-group-dialog_test.html
index ebca289..3a99526 100644
--- a/polygerrit-ui/app/elements/admin/gr-create-group-dialog/gr-create-group-dialog_test.html
+++ b/polygerrit-ui/app/elements/admin/gr-create-group-dialog/gr-create-group-dialog_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-create-group-dialog</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/page/page.js"></script>
@@ -56,7 +56,8 @@
     test('name is updated correctly', done => {
       assert.isFalse(element.hasNewGroupName);
 
-      ironInput(element.root).bindValue = GROUP_NAME;
+      const inputEl = element.root.querySelector('iron-input');
+      inputEl.bindValue = GROUP_NAME;
 
       setTimeout(() => {
         assert.isTrue(element.hasNewGroupName);
diff --git a/polygerrit-ui/app/elements/admin/gr-create-pointer-dialog/gr-create-pointer-dialog.js b/polygerrit-ui/app/elements/admin/gr-create-pointer-dialog/gr-create-pointer-dialog.js
index 4e9da90..56d7bc8 100644
--- a/polygerrit-ui/app/elements/admin/gr-create-pointer-dialog/gr-create-pointer-dialog.js
+++ b/polygerrit-ui/app/elements/admin/gr-create-pointer-dialog/gr-create-pointer-dialog.js
@@ -22,35 +22,43 @@
     tags: 'tags',
   };
 
-  Polymer({
-    is: 'gr-create-pointer-dialog',
+  /**
+    * @appliesMixin Gerrit.BaseUrlMixin
+    * @appliesMixin Gerrit.URLEncodingMixin
+    */
+  class GrCreatePointerDialog extends Polymer.mixinBehaviors( [
+    Gerrit.BaseUrlBehavior,
+    Gerrit.URLEncodingBehavior,
+  ], Polymer.GestureEventListeners(
+      Polymer.LegacyElementMixin(
+          Polymer.Element))) {
+    static get is() { return 'gr-create-pointer-dialog'; }
 
-    properties: {
-      detailType: String,
-      repoName: String,
-      hasNewItemName: {
-        type: Boolean,
-        notify: true,
-        value: false,
-      },
-      itemDetail: String,
-      _itemName: String,
-      _itemRevision: String,
-      _itemAnnotation: String,
-    },
+    static get properties() {
+      return {
+        detailType: String,
+        repoName: String,
+        hasNewItemName: {
+          type: Boolean,
+          notify: true,
+          value: false,
+        },
+        itemDetail: String,
+        _itemName: String,
+        _itemRevision: String,
+        _itemAnnotation: String,
+      };
+    }
 
-    behaviors: [
-      Gerrit.BaseUrlBehavior,
-      Gerrit.URLEncodingBehavior,
-    ],
-
-    observers: [
-      '_updateItemName(_itemName)',
-    ],
+    static get observers() {
+      return [
+        '_updateItemName(_itemName)',
+      ];
+    }
 
     _updateItemName(name) {
       this.hasNewItemName = !!name;
-    },
+    }
 
     _computeItemUrl(project) {
       if (this.itemDetail === DETAIL_TYPES.branches) {
@@ -60,7 +68,7 @@
         return this.getBaseUrl() + '/admin/repos/' +
             this.encodeURL(this.repoName, true) + ',tags';
       }
-    },
+    }
 
     handleCreateItem() {
       const USE_HEAD = this._itemRevision ? this._itemRevision : 'HEAD';
@@ -82,10 +90,12 @@
               }
             });
       }
-    },
+    }
 
     _computeHideItemClass(type) {
       return type === DETAIL_TYPES.branches ? 'hideItem' : '';
-    },
-  });
+    }
+  }
+
+  customElements.define(GrCreatePointerDialog.is, GrCreatePointerDialog);
 })();
diff --git a/polygerrit-ui/app/elements/admin/gr-create-pointer-dialog/gr-create-pointer-dialog_test.html b/polygerrit-ui/app/elements/admin/gr-create-pointer-dialog/gr-create-pointer-dialog_test.html
index 08e8213..41b8eca 100644
--- a/polygerrit-ui/app/elements/admin/gr-create-pointer-dialog/gr-create-pointer-dialog_test.html
+++ b/polygerrit-ui/app/elements/admin/gr-create-pointer-dialog/gr-create-pointer-dialog_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-create-pointer-dialog</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>
@@ -39,6 +39,10 @@
     let element;
     let sandbox;
 
+    const ironInput = function(element) {
+      return Polymer.dom(element).querySelector('iron-input');
+    };
+
     setup(() => {
       sandbox = sinon.sandbox.create();
       stub('gr-rest-api-interface', {
diff --git a/polygerrit-ui/app/elements/admin/gr-create-repo-dialog/gr-create-repo-dialog.js b/polygerrit-ui/app/elements/admin/gr-create-repo-dialog/gr-create-repo-dialog.js
index bb2b5f2..fd6dc4a 100644
--- a/polygerrit-ui/app/elements/admin/gr-create-repo-dialog/gr-create-repo-dialog.js
+++ b/polygerrit-ui/app/elements/admin/gr-create-repo-dialog/gr-create-repo-dialog.js
@@ -17,69 +17,77 @@
 (function() {
   'use strict';
 
-  Polymer({
-    is: 'gr-create-repo-dialog',
+  /**
+    * @appliesMixin Gerrit.BaseUrlMixin
+    * @appliesMixin Gerrit.URLEncodingMixin
+    */
+  class GrCreateRepoDialog extends Polymer.mixinBehaviors( [
+    Gerrit.BaseUrlBehavior,
+    Gerrit.URLEncodingBehavior,
+  ], Polymer.GestureEventListeners(
+      Polymer.LegacyElementMixin(
+          Polymer.Element))) {
+    static get is() { return 'gr-create-repo-dialog'; }
 
-    properties: {
-      params: Object,
-      hasNewRepoName: {
-        type: Boolean,
-        notify: true,
-        value: false,
-      },
+    static get properties() {
+      return {
+        params: Object,
+        hasNewRepoName: {
+          type: Boolean,
+          notify: true,
+          value: false,
+        },
 
-      /** @type {?} */
-      _repoConfig: {
-        type: Object,
-        value: () => {
+        /** @type {?} */
+        _repoConfig: {
+          type: Object,
+          value: () => {
           // Set default values for dropdowns.
-          return {
-            create_empty_commit: true,
-            permissions_only: false,
-          };
+            return {
+              create_empty_commit: true,
+              permissions_only: false,
+            };
+          },
         },
-      },
-      _repoCreated: {
-        type: Boolean,
-        value: false,
-      },
-      _repoOwner: String,
-      _repoOwnerId: {
-        type: String,
-        observer: '_repoOwnerIdUpdate',
-      },
-
-      _query: {
-        type: Function,
-        value() {
-          return this._getRepoSuggestions.bind(this);
+        _repoCreated: {
+          type: Boolean,
+          value: false,
         },
-      },
-      _queryGroups: {
-        type: Function,
-        value() {
-          return this._getGroupSuggestions.bind(this);
+        _repoOwner: String,
+        _repoOwnerId: {
+          type: String,
+          observer: '_repoOwnerIdUpdate',
         },
-      },
-    },
 
-    observers: [
-      '_updateRepoName(_repoConfig.name)',
-    ],
+        _query: {
+          type: Function,
+          value() {
+            return this._getRepoSuggestions.bind(this);
+          },
+        },
+        _queryGroups: {
+          type: Function,
+          value() {
+            return this._getGroupSuggestions.bind(this);
+          },
+        },
+      };
+    }
 
-    behaviors: [
-      Gerrit.BaseUrlBehavior,
-      Gerrit.URLEncodingBehavior,
-    ],
+    static get observers() {
+      return [
+        '_updateRepoName(_repoConfig.name)',
+      ];
+    }
 
     _computeRepoUrl(repoName) {
       return this.getBaseUrl() + '/admin/repos/' +
           this.encodeURL(repoName, true);
-    },
+    }
 
     _updateRepoName(name) {
       this.hasNewRepoName = !!name;
-    },
+    }
 
     _repoOwnerIdUpdate(id) {
       if (id) {
@@ -87,7 +95,7 @@
       } else {
         this.set('_repoConfig.owners', undefined);
       }
-    },
+    }
 
     handleCreateRepo() {
       return this.$.restAPI.createRepo(this._repoConfig)
@@ -97,7 +105,7 @@
               page.show(this._computeRepoUrl(this._repoConfig.name));
             }
           });
-    },
+    }
 
     _getRepoSuggestions(input) {
       return this.$.restAPI.getSuggestedProjects(input)
@@ -112,7 +120,7 @@
             }
             return repos;
           });
-    },
+    }
 
     _getGroupSuggestions(input) {
       return this.$.restAPI.getSuggestedGroups(input)
@@ -127,6 +135,8 @@
             }
             return groups;
           });
-    },
-  });
+    }
+  }
+
+  customElements.define(GrCreateRepoDialog.is, GrCreateRepoDialog);
 })();
diff --git a/polygerrit-ui/app/elements/admin/gr-create-repo-dialog/gr-create-repo-dialog_test.html b/polygerrit-ui/app/elements/admin/gr-create-repo-dialog/gr-create-repo-dialog_test.html
index 7e32c5c..fa481c2 100644
--- a/polygerrit-ui/app/elements/admin/gr-create-repo-dialog/gr-create-repo-dialog_test.html
+++ b/polygerrit-ui/app/elements/admin/gr-create-repo-dialog/gr-create-repo-dialog_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-create-repo-dialog</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>
diff --git a/polygerrit-ui/app/elements/admin/gr-group-audit-log/gr-group-audit-log.js b/polygerrit-ui/app/elements/admin/gr-group-audit-log/gr-group-audit-log.js
index 8901d4a..e0c980c 100644
--- a/polygerrit-ui/app/elements/admin/gr-group-audit-log/gr-group-audit-log.js
+++ b/polygerrit-ui/app/elements/admin/gr-group-audit-log/gr-group-audit-log.js
@@ -19,30 +19,38 @@
 
   const GROUP_EVENTS = ['ADD_GROUP', 'REMOVE_GROUP'];
 
-  Polymer({
-    is: 'gr-group-audit-log',
+  /**
+    * @appliesMixin Gerrit.FireMixin
+    * @appliesMixin Gerrit.ListViewMixin
+    */
+  class GrGroupAuditLog extends Polymer.mixinBehaviors( [
+    Gerrit.FireBehavior,
+    Gerrit.ListViewBehavior,
+  ], Polymer.GestureEventListeners(
+      Polymer.LegacyElementMixin(
+          Polymer.Element))) {
+    static get is() { return 'gr-group-audit-log'; }
 
-    properties: {
-      groupId: String,
-      _auditLog: Array,
-      _loading: {
-        type: Boolean,
-        value: true,
-      },
-    },
-
-    behaviors: [
-      Gerrit.FireBehavior,
-      Gerrit.ListViewBehavior,
-    ],
+    static get properties() {
+      return {
+        groupId: String,
+        _auditLog: Array,
+        _loading: {
+          type: Boolean,
+          value: true,
+        },
+      };
+    }
 
     attached() {
+      super.attached();
       this.fire('title-change', {title: 'Audit Log'});
-    },
+    }
 
     ready() {
+      super.ready();
       this._getAuditLogs();
-    },
+    }
 
     _getAuditLogs() {
       if (!this.groupId) { return ''; }
@@ -60,11 +68,11 @@
             this._auditLog = auditLog;
             this._loading = false;
           });
-    },
+    }
 
     _status(item) {
       return item.disabled ? 'Disabled' : 'Enabled';
-    },
+    }
 
     itemType(type) {
       let item;
@@ -81,11 +89,11 @@
           item = '';
       }
       return item;
-    },
+    }
 
     _isGroupEvent(type) {
       return GROUP_EVENTS.indexOf(type) !== -1;
-    },
+    }
 
     _computeGroupUrl(group) {
       if (group && group.url && group.id) {
@@ -93,11 +101,11 @@
       }
 
       return '';
-    },
+    }
 
     _getIdForUser(account) {
       return account._account_id ? ' (' + account._account_id + ')' : '';
-    },
+    }
 
     _getNameForGroup(group) {
       if (group && group.name) {
@@ -108,6 +116,8 @@
       }
 
       return '';
-    },
-  });
+    }
+  }
+
+  customElements.define(GrGroupAuditLog.is, GrGroupAuditLog);
 })();
diff --git a/polygerrit-ui/app/elements/admin/gr-group-audit-log/gr-group-audit-log_test.html b/polygerrit-ui/app/elements/admin/gr-group-audit-log/gr-group-audit-log_test.html
index 313d465..d517242 100644
--- a/polygerrit-ui/app/elements/admin/gr-group-audit-log/gr-group-audit-log_test.html
+++ b/polygerrit-ui/app/elements/admin/gr-group-audit-log/gr-group-audit-log_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-group-audit-log</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>
diff --git a/polygerrit-ui/app/elements/admin/gr-group-members/gr-group-members.js b/polygerrit-ui/app/elements/admin/gr-group-members/gr-group-members.js
index 7f8e9ac..574bc29 100644
--- a/polygerrit-ui/app/elements/admin/gr-group-members/gr-group-members.js
+++ b/polygerrit-ui/app/elements/admin/gr-group-members/gr-group-members.js
@@ -23,57 +23,65 @@
 
   const URL_REGEX = '^(?:[a-z]+:)?//';
 
-  Polymer({
-    is: 'gr-group-members',
+  /**
+    * @appliesMixin Gerrit.BaseUrlMixin
+    * @appliesMixin Gerrit.FireMixin
+    * @appliesMixin Gerrit.URLEncodingMixin
+    */
+  class GrGroupMembers extends Polymer.mixinBehaviors( [
+    Gerrit.BaseUrlBehavior,
+    Gerrit.FireBehavior,
+    Gerrit.URLEncodingBehavior,
+  ], Polymer.GestureEventListeners(
+      Polymer.LegacyElementMixin(
+          Polymer.Element))) {
+    static get is() { return 'gr-group-members'; }
 
-    properties: {
-      groupId: Number,
-      _groupMemberSearchId: String,
-      _groupMemberSearchName: String,
-      _includedGroupSearchId: String,
-      _includedGroupSearchName: String,
-      _loading: {
-        type: Boolean,
-        value: true,
-      },
-      _groupName: String,
-      _groupMembers: Object,
-      _includedGroups: Object,
-      _itemName: String,
-      _itemType: String,
-      _queryMembers: {
-        type: Function,
-        value() {
-          return this._getAccountSuggestions.bind(this);
+    static get properties() {
+      return {
+        groupId: Number,
+        _groupMemberSearchId: String,
+        _groupMemberSearchName: String,
+        _includedGroupSearchId: String,
+        _includedGroupSearchName: String,
+        _loading: {
+          type: Boolean,
+          value: true,
         },
-      },
-      _queryIncludedGroup: {
-        type: Function,
-        value() {
-          return this._getGroupSuggestions.bind(this);
+        _groupName: String,
+        _groupMembers: Object,
+        _includedGroups: Object,
+        _itemName: String,
+        _itemType: String,
+        _queryMembers: {
+          type: Function,
+          value() {
+            return this._getAccountSuggestions.bind(this);
+          },
         },
-      },
-      _groupOwner: {
-        type: Boolean,
-        value: false,
-      },
-      _isAdmin: {
-        type: Boolean,
-        value: false,
-      },
-    },
-
-    behaviors: [
-      Gerrit.BaseUrlBehavior,
-      Gerrit.FireBehavior,
-      Gerrit.URLEncodingBehavior,
-    ],
+        _queryIncludedGroup: {
+          type: Function,
+          value() {
+            return this._getGroupSuggestions.bind(this);
+          },
+        },
+        _groupOwner: {
+          type: Boolean,
+          value: false,
+        },
+        _isAdmin: {
+          type: Boolean,
+          value: false,
+        },
+      };
+    }
 
     attached() {
+      super.attached();
       this._loadGroupDetails();
 
       this.fire('title-change', {title: 'Members'});
-    },
+    }
 
     _loadGroupDetails() {
       if (!this.groupId) { return; }
@@ -113,15 +121,15 @@
               this._loading = false;
             });
           });
-    },
+    }
 
     _computeLoadingClass(loading) {
       return loading ? 'loading' : '';
-    },
+    }
 
     _isLoading() {
       return this._loading || this._loading === undefined;
-    },
+    }
 
     _computeGroupUrl(url) {
       if (!url) { return; }
@@ -136,7 +144,7 @@
         return this.getBaseUrl() + url.slice(1);
       }
       return this.getBaseUrl() + url;
-    },
+    }
 
     _handleSavingGroupMember() {
       return this.$.restAPI.saveGroupMembers(this._groupName,
@@ -150,7 +158,7 @@
         this._groupMemberSearchName = '';
         this._groupMemberSearchId = '';
       });
-    },
+    }
 
     _handleDeleteConfirm() {
       this.$.overlay.close();
@@ -177,11 +185,11 @@
               }
             });
       }
-    },
+    }
 
     _handleConfirmDialogCancel() {
       this.$.overlay.close();
-    },
+    }
 
     _handleDeleteMember(e) {
       const id = e.model.get('item._account_id');
@@ -196,7 +204,7 @@
       this._itemId = id;
       this._itemType = 'member';
       this.$.overlay.open();
-    },
+    }
 
     _handleSavingIncludedGroups() {
       return this.$.restAPI.saveIncludedGroup(this._groupName,
@@ -222,7 +230,7 @@
             this._includedGroupSearchName = '';
             this._includedGroupSearchId = '';
           });
-    },
+    }
 
     _handleDeleteIncludedGroup(e) {
       const id = decodeURIComponent(e.model.get('item.id')).replace(/\+/g, ' ');
@@ -233,7 +241,7 @@
       this._itemId = id;
       this._itemType = 'includedGroup';
       this.$.overlay.open();
-    },
+    }
 
     _getAccountSuggestions(input) {
       if (input.length === 0) { return Promise.resolve([]); }
@@ -257,7 +265,7 @@
         }
         return accountSuggestions;
       });
-    },
+    }
 
     _getGroupSuggestions(input) {
       return this.$.restAPI.getSuggestedGroups(input)
@@ -272,10 +280,12 @@
             }
             return groups;
           });
-    },
+    }
 
     _computeHideItemClass(owner, admin) {
       return admin || owner ? '' : 'canModify';
-    },
-  });
+    }
+  }
+
+  customElements.define(GrGroupMembers.is, GrGroupMembers);
 })();
diff --git a/polygerrit-ui/app/elements/admin/gr-group-members/gr-group-members_test.html b/polygerrit-ui/app/elements/admin/gr-group-members/gr-group-members_test.html
index bf9113b..e90e6fd 100644
--- a/polygerrit-ui/app/elements/admin/gr-group-members/gr-group-members_test.html
+++ b/polygerrit-ui/app/elements/admin/gr-group-members/gr-group-members_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-group-members</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>
diff --git a/polygerrit-ui/app/elements/admin/gr-group/gr-group.js b/polygerrit-ui/app/elements/admin/gr-group/gr-group.js
index 19cb45c..760ab9fa 100644
--- a/polygerrit-ui/app/elements/admin/gr-group/gr-group.js
+++ b/polygerrit-ui/app/elements/admin/gr-group/gr-group.js
@@ -30,78 +30,85 @@
     },
   };
 
-  Polymer({
-    is: 'gr-group',
-
+  /**
+    * @appliesMixin Gerrit.FireMixin
+    */
+  class GrGroup extends Polymer.mixinBehaviors( [
+    Gerrit.FireBehavior,
+  ], Polymer.GestureEventListeners(
+      Polymer.LegacyElementMixin(
+          Polymer.Element))) {
+    static get is() { return 'gr-group'; }
     /**
      * Fired when the group name changes.
      *
      * @event name-changed
      */
 
-    properties: {
-      groupId: Number,
-      _rename: {
-        type: Boolean,
-        value: false,
-      },
-      _groupIsInternal: Boolean,
-      _description: {
-        type: Boolean,
-        value: false,
-      },
-      _owner: {
-        type: Boolean,
-        value: false,
-      },
-      _options: {
-        type: Boolean,
-        value: false,
-      },
-      _loading: {
-        type: Boolean,
-        value: true,
-      },
-      /** @type {?} */
-      _groupConfig: Object,
-      _groupConfigOwner: String,
-      _groupName: Object,
-      _groupOwner: {
-        type: Boolean,
-        value: false,
-      },
-      _submitTypes: {
-        type: Array,
-        value() {
-          return Object.values(OPTIONS);
+    static get properties() {
+      return {
+        groupId: Number,
+        _rename: {
+          type: Boolean,
+          value: false,
         },
-      },
-      _query: {
-        type: Function,
-        value() {
-          return this._getGroupSuggestions.bind(this);
+        _groupIsInternal: Boolean,
+        _description: {
+          type: Boolean,
+          value: false,
         },
-      },
-      _isAdmin: {
-        type: Boolean,
-        value: false,
-      },
-    },
+        _owner: {
+          type: Boolean,
+          value: false,
+        },
+        _options: {
+          type: Boolean,
+          value: false,
+        },
+        _loading: {
+          type: Boolean,
+          value: true,
+        },
+        /** @type {?} */
+        _groupConfig: Object,
+        _groupConfigOwner: String,
+        _groupName: Object,
+        _groupOwner: {
+          type: Boolean,
+          value: false,
+        },
+        _submitTypes: {
+          type: Array,
+          value() {
+            return Object.values(OPTIONS);
+          },
+        },
+        _query: {
+          type: Function,
+          value() {
+            return this._getGroupSuggestions.bind(this);
+          },
+        },
+        _isAdmin: {
+          type: Boolean,
+          value: false,
+        },
+      };
+    }
 
-    behaviors: [
-      Gerrit.FireBehavior,
-    ],
-
-    observers: [
-      '_handleConfigName(_groupConfig.name)',
-      '_handleConfigOwner(_groupConfig.owner, _groupConfigOwner)',
-      '_handleConfigDescription(_groupConfig.description)',
-      '_handleConfigOptions(_groupConfig.options.visible_to_all)',
-    ],
+    static get observers() {
+      return [
+        '_handleConfigName(_groupConfig.name)',
+        '_handleConfigOwner(_groupConfig.owner, _groupConfigOwner)',
+        '_handleConfigDescription(_groupConfig.description)',
+        '_handleConfigOptions(_groupConfig.options.visible_to_all)',
+      ];
+    }
 
     attached() {
+      super.attached();
       this._loadGroup();
-    },
+    }
 
     _loadGroup() {
       if (!this.groupId) { return; }
@@ -143,15 +150,15 @@
               this._loading = false;
             });
           });
-    },
+    }
 
     _computeLoadingClass(loading) {
       return loading ? 'loading' : '';
-    },
+    }
 
     _isLoading() {
       return this._loading || this._loading === undefined;
-    },
+    }
 
     _handleSaveName() {
       return this.$.restAPI.saveGroupName(this.groupId, this._groupConfig.name)
@@ -163,7 +170,7 @@
               this._rename = false;
             }
           });
-    },
+    }
 
     _handleSaveOwner() {
       let owner = this._groupConfig.owner;
@@ -174,14 +181,14 @@
           owner).then(config => {
         this._owner = false;
       });
-    },
+    }
 
     _handleSaveDescription() {
       return this.$.restAPI.saveGroupDescription(this.groupId,
           this._groupConfig.description).then(config => {
         this._description = false;
       });
-    },
+    }
 
     _handleSaveOptions() {
       const visible = this._groupConfig.options.visible_to_all;
@@ -192,31 +199,31 @@
           options).then(config => {
         this._options = false;
       });
-    },
+    }
 
     _handleConfigName() {
       if (this._isLoading()) { return; }
       this._rename = true;
-    },
+    }
 
     _handleConfigOwner() {
       if (this._isLoading()) { return; }
       this._owner = true;
-    },
+    }
 
     _handleConfigDescription() {
       if (this._isLoading()) { return; }
       this._description = true;
-    },
+    }
 
     _handleConfigOptions() {
       if (this._isLoading()) { return; }
       this._options = true;
-    },
+    }
 
     _computeHeaderClass(configChanged) {
       return configChanged ? 'edited' : '';
-    },
+    }
 
     _getGroupSuggestions(input) {
       return this.$.restAPI.getSuggestedGroups(input)
@@ -231,10 +238,12 @@
             }
             return groups;
           });
-    },
+    }
 
     _computeGroupDisabled(owner, admin, groupIsInternal) {
       return groupIsInternal && (admin || owner) ? false : true;
-    },
-  });
+    }
+  }
+
+  customElements.define(GrGroup.is, GrGroup);
 })();
diff --git a/polygerrit-ui/app/elements/admin/gr-group/gr-group_test.html b/polygerrit-ui/app/elements/admin/gr-group/gr-group_test.html
index 1672e85..0d7aae2 100644
--- a/polygerrit-ui/app/elements/admin/gr-group/gr-group_test.html
+++ b/polygerrit-ui/app/elements/admin/gr-group/gr-group_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-group</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>
diff --git a/polygerrit-ui/app/elements/admin/gr-permission/gr-permission.js b/polygerrit-ui/app/elements/admin/gr-permission/gr-permission.js
index c485b15..e1e7c0a 100644
--- a/polygerrit-ui/app/elements/admin/gr-permission/gr-permission.js
+++ b/polygerrit-ui/app/elements/admin/gr-permission/gr-permission.js
@@ -25,91 +25,100 @@
   ];
 
   /**
+    * @appliesMixin Gerrit.AccessMixin
+    * @appliesMixin Gerrit.FireMixin
+    */
+  /**
    * Fired when the permission has been modified or removed.
    *
    * @event access-modified
    */
-
   /**
    * Fired when a permission that was previously added was removed.
    * @event added-permission-removed
    */
-
-  Polymer({
-    is: 'gr-permission',
-
-    properties: {
-      labels: Object,
-      name: String,
-      /** @type {?} */
-      permission: {
-        type: Object,
-        observer: '_sortPermission',
-        notify: true,
-      },
-      groups: Object,
-      section: String,
-      editing: {
-        type: Boolean,
-        value: false,
-        observer: '_handleEditingChanged',
-      },
-      _label: {
-        type: Object,
-        computed: '_computeLabel(permission, labels)',
-      },
-      _groupFilter: String,
-      _query: {
-        type: Function,
-        value() {
-          return this._getGroupSuggestions.bind(this);
-        },
-      },
-      _rules: Array,
-      _groupsWithRules: Object,
-      _deleted: {
-        type: Boolean,
-        value: false,
-      },
-      _originalExclusiveValue: Boolean,
-    },
-
-    behaviors: [
-      Gerrit.AccessBehavior,
-      /**
+  class GrPermission extends Polymer.mixinBehaviors( [
+    Gerrit.AccessBehavior,
+    /**
        * Unused in this element, but called by other elements in tests
        * e.g gr-access-section_test.
        */
-      Gerrit.FireBehavior,
-    ],
+    Gerrit.FireBehavior,
+  ], Polymer.GestureEventListeners(
+      Polymer.LegacyElementMixin(
+          Polymer.Element))) {
+    static get is() { return 'gr-permission'; }
 
-    observers: [
-      '_handleRulesChanged(_rules.splices)',
-    ],
+    static get properties() {
+      return {
+        labels: Object,
+        name: String,
+        /** @type {?} */
+        permission: {
+          type: Object,
+          observer: '_sortPermission',
+          notify: true,
+        },
+        groups: Object,
+        section: String,
+        editing: {
+          type: Boolean,
+          value: false,
+          observer: '_handleEditingChanged',
+        },
+        _label: {
+          type: Object,
+          computed: '_computeLabel(permission, labels)',
+        },
+        _groupFilter: String,
+        _query: {
+          type: Function,
+          value() {
+            return this._getGroupSuggestions.bind(this);
+          },
+        },
+        _rules: Array,
+        _groupsWithRules: Object,
+        _deleted: {
+          type: Boolean,
+          value: false,
+        },
+        _originalExclusiveValue: Boolean,
+      };
+    }
 
-    listeners: {
-      'access-saved': '_handleAccessSaved',
-    },
+    static get observers() {
+      return [
+        '_handleRulesChanged(_rules.splices)',
+      ];
+    }
+
+    created() {
+      super.created();
+      this.addEventListener('access-saved',
+          () => this._handleAccessSaved());
+    }
 
     ready() {
+      super.ready();
       this._setupValues();
-    },
+    }
 
     _setupValues() {
       if (!this.permission) { return; }
       this._originalExclusiveValue = !!this.permission.value.exclusive;
       Polymer.dom.flush();
-    },
+    }
 
     _handleAccessSaved() {
       // Set a new 'original' value to keep track of after the value has been
       // saved.
       this._setupValues();
-    },
+    }
 
     _permissionIsOwnerOrGlobal(permissionId, section) {
       return permissionId === 'owner' || section === 'GLOBAL_CAPABILITIES';
-    },
+    }
 
     _handleEditingChanged(editing, editingOld) {
       // Ignore when editing gets set initially.
@@ -130,20 +139,20 @@
         this.set(['permission', 'value', 'exclusive'],
             this._originalExclusiveValue);
       }
-    },
+    }
 
     _handleAddedRuleRemoved(e) {
       const index = e.model.index;
       this._rules = this._rules.slice(0, index)
           .concat(this._rules.slice(index + 1, this._rules.length));
-    },
+    }
 
     _handleValueChange() {
       this.permission.value.modified = true;
       // Allows overall access page to know a change has been made.
       this.dispatchEvent(
           new CustomEvent('access-modified', {bubbles: true, composed: true}));
-    },
+    }
 
     _handleRemovePermission() {
       if (this.permission.value.added) {
@@ -154,16 +163,16 @@
       this.permission.value.deleted = true;
       this.dispatchEvent(
           new CustomEvent('access-modified', {bubbles: true, composed: true}));
-    },
+    }
 
     _handleRulesChanged(changeRecord) {
       // Update the groups to exclude in the autocomplete.
       this._groupsWithRules = this._computeGroupsWithRules(this._rules);
-    },
+    }
 
     _sortPermission(permission) {
       this._rules = this.toSortedArray(permission.value.rules);
-    },
+    }
 
     _computeSectionClass(editing, deleted) {
       const classList = [];
@@ -174,12 +183,12 @@
         classList.push('deleted');
       }
       return classList.join(' ');
-    },
+    }
 
     _handleUndoRemove() {
       this._deleted = false;
       delete this.permission.value.deleted;
-    },
+    }
 
     _computeLabel(permission, labels) {
       if (!labels || !permission ||
@@ -195,7 +204,7 @@
         values: this._computeLabelValues(labels[labelName].values),
       };
       return label;
-    },
+    }
 
     _computeLabelValues(values) {
       const valuesArr = [];
@@ -211,7 +220,7 @@
         valuesArr.push({value: parseInt(key, 10), text});
       }
       return valuesArr;
-    },
+    }
 
     /**
      * @param {!Array} rules
@@ -224,12 +233,12 @@
         groups[rule.id] = true;
       }
       return groups;
-    },
+    }
 
     _computeGroupName(groups, groupId) {
       return groups && groups[groupId] && groups[groupId].name ?
         groups[groupId].name : groupId;
-    },
+    }
 
     _getGroupSuggestions() {
       return this.$.restAPI.getSuggestedGroups(
@@ -249,7 +258,7 @@
               return !this._groupsWithRules[group.value.id];
             });
           });
-    },
+    }
 
     /**
      * Handles adding a skeleton item to the dom-repeat.
@@ -283,12 +292,14 @@
       this.set(['permission', 'value', 'rules', groupId], value);
       this.dispatchEvent(
           new CustomEvent('access-modified', {bubbles: true, composed: true}));
-    },
+    }
 
     _computeHasRange(name) {
       if (!name) { return false; }
 
       return RANGE_NAMES.includes(name.toUpperCase());
-    },
-  });
+    }
+  }
+
+  customElements.define(GrPermission.is, GrPermission);
 })();
diff --git a/polygerrit-ui/app/elements/admin/gr-permission/gr-permission_test.html b/polygerrit-ui/app/elements/admin/gr-permission/gr-permission_test.html
index 8e57534..6cb7e4a 100644
--- a/polygerrit-ui/app/elements/admin/gr-permission/gr-permission_test.html
+++ b/polygerrit-ui/app/elements/admin/gr-permission/gr-permission_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-permission</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/page/page.js"></script>
diff --git a/polygerrit-ui/app/elements/admin/gr-plugin-config-array-editor/gr-plugin-config-array-editor.js b/polygerrit-ui/app/elements/admin/gr-plugin-config-array-editor/gr-plugin-config-array-editor.js
index bb0d501..854bbbc 100644
--- a/polygerrit-ui/app/elements/admin/gr-plugin-config-array-editor/gr-plugin-config-array-editor.js
+++ b/polygerrit-ui/app/elements/admin/gr-plugin-config-array-editor/gr-plugin-config-array-editor.js
@@ -17,39 +17,42 @@
 (function() {
   'use strict';
 
-  Polymer({
-    is: 'gr-plugin-config-array-editor',
-
+  class GrPluginConfigArrayEditor extends Polymer.GestureEventListeners(
+      Polymer.LegacyElementMixin(
+          Polymer.Element)) {
+    static get is() { return 'gr-plugin-config-array-editor'; }
     /**
      * Fired when the plugin config option changes.
      *
      * @event plugin-config-option-changed
      */
 
-    properties: {
+    static get properties() {
+      return {
       /** @type {?} */
-      pluginOption: Object,
-      /** @type {Boolean} */
-      disabled: {
-        type: Boolean,
-        computed: '_computeDisabled(pluginOption.*)',
-      },
-      /** @type {?} */
-      _newValue: {
-        type: String,
-        value: '',
-      },
-    },
+        pluginOption: Object,
+        /** @type {Boolean} */
+        disabled: {
+          type: Boolean,
+          computed: '_computeDisabled(pluginOption.*)',
+        },
+        /** @type {?} */
+        _newValue: {
+          type: String,
+          value: '',
+        },
+      };
+    }
 
     _computeDisabled(record) {
       return !(record && record.base && record.base.info &&
           record.base.info.editable);
-    },
+    }
 
     _handleAddTap(e) {
       e.preventDefault();
       this._handleAdd();
-    },
+    }
 
     _handleInputKeydown(e) {
       // Enter.
@@ -57,20 +60,20 @@
         e.preventDefault();
         this._handleAdd();
       }
-    },
+    }
 
     _handleAdd() {
       if (!this._newValue.length) { return; }
       this._dispatchChanged(
           this.pluginOption.info.values.concat([this._newValue]));
       this._newValue = '';
-    },
+    }
 
     _handleDelete(e) {
       const value = Polymer.dom(e).localTarget.dataset.item;
       this._dispatchChanged(
           this.pluginOption.info.values.filter(str => str !== value));
-    },
+    }
 
     _dispatchChanged(values) {
       const {_key, info} = this.pluginOption;
@@ -81,10 +84,13 @@
       };
       this.dispatchEvent(
           new CustomEvent('plugin-config-option-changed', {detail}));
-    },
+    }
 
     _computeShowInputRow(disabled) {
       return disabled ? 'hide' : '';
-    },
-  });
+    }
+  }
+
+  customElements.define(GrPluginConfigArrayEditor.is,
+      GrPluginConfigArrayEditor);
 })();
diff --git a/polygerrit-ui/app/elements/admin/gr-plugin-config-array-editor/gr-plugin-config-array-editor_test.html b/polygerrit-ui/app/elements/admin/gr-plugin-config-array-editor/gr-plugin-config-array-editor_test.html
index 39e4ddc..5198ad0 100644
--- a/polygerrit-ui/app/elements/admin/gr-plugin-config-array-editor/gr-plugin-config-array-editor_test.html
+++ b/polygerrit-ui/app/elements/admin/gr-plugin-config-array-editor/gr-plugin-config-array-editor_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-plugin-config-array-editor</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>
diff --git a/polygerrit-ui/app/elements/admin/gr-plugin-list/gr-plugin-list.js b/polygerrit-ui/app/elements/admin/gr-plugin-list/gr-plugin-list.js
index 1dbfdc8..e0089dc 100644
--- a/polygerrit-ui/app/elements/admin/gr-plugin-list/gr-plugin-list.js
+++ b/polygerrit-ui/app/elements/admin/gr-plugin-list/gr-plugin-list.js
@@ -17,60 +17,67 @@
 (function() {
   'use strict';
 
-  Polymer({
-    is: 'gr-plugin-list',
+  /**
+    * @appliesMixin Gerrit.FireMixin
+    * @appliesMixin Gerrit.ListViewMixin
+    */
+  class GrPluginList extends Polymer.mixinBehaviors( [
+    Gerrit.FireBehavior,
+    Gerrit.ListViewBehavior,
+  ], Polymer.GestureEventListeners(
+      Polymer.LegacyElementMixin(
+          Polymer.Element))) {
+    static get is() { return 'gr-plugin-list'; }
 
-    properties: {
+    static get properties() {
+      return {
       /**
        * URL params passed from the router.
        */
-      params: {
-        type: Object,
-        observer: '_paramsChanged',
-      },
-      /**
+        params: {
+          type: Object,
+          observer: '_paramsChanged',
+        },
+        /**
        * Offset of currently visible query results.
        */
-      _offset: {
-        type: Number,
-        value: 0,
-      },
-      _path: {
-        type: String,
-        readOnly: true,
-        value: '/admin/plugins',
-      },
-      _plugins: Array,
-      /**
+        _offset: {
+          type: Number,
+          value: 0,
+        },
+        _path: {
+          type: String,
+          readOnly: true,
+          value: '/admin/plugins',
+        },
+        _plugins: Array,
+        /**
        * Because  we request one more than the pluginsPerPage, _shownPlugins
        * maybe one less than _plugins.
        * */
-      _shownPlugins: {
-        type: Array,
-        computed: 'computeShownItems(_plugins)',
-      },
-      _pluginsPerPage: {
-        type: Number,
-        value: 25,
-      },
-      _loading: {
-        type: Boolean,
-        value: true,
-      },
-      _filter: {
-        type: String,
-        value: '',
-      },
-    },
-
-    behaviors: [
-      Gerrit.FireBehavior,
-      Gerrit.ListViewBehavior,
-    ],
+        _shownPlugins: {
+          type: Array,
+          computed: 'computeShownItems(_plugins)',
+        },
+        _pluginsPerPage: {
+          type: Number,
+          value: 25,
+        },
+        _loading: {
+          type: Boolean,
+          value: true,
+        },
+        _filter: {
+          type: String,
+          value: '',
+        },
+      };
+    }
 
     attached() {
+      super.attached();
       this.fire('title-change', {title: 'Plugins'});
-    },
+    }
 
     _paramsChanged(params) {
       this._loading = true;
@@ -79,7 +86,7 @@
 
       return this._getPlugins(this._filter, this._pluginsPerPage,
           this._offset);
-    },
+    }
 
     _getPlugins(filter, pluginsPerPage, offset) {
       const errFn = response => {
@@ -99,14 +106,16 @@
                 });
             this._loading = false;
           });
-    },
+    }
 
     _status(item) {
       return item.disabled === true ? 'Disabled' : 'Enabled';
-    },
+    }
 
     _computePluginUrl(id) {
       return this.getUrl('/', id);
-    },
-  });
+    }
+  }
+
+  customElements.define(GrPluginList.is, GrPluginList);
 })();
diff --git a/polygerrit-ui/app/elements/admin/gr-plugin-list/gr-plugin-list_test.html b/polygerrit-ui/app/elements/admin/gr-plugin-list/gr-plugin-list_test.html
index 96fff60..98636a8 100644
--- a/polygerrit-ui/app/elements/admin/gr-plugin-list/gr-plugin-list_test.html
+++ b/polygerrit-ui/app/elements/admin/gr-plugin-list/gr-plugin-list_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-plugin-list</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/page/page.js"></script>
diff --git a/polygerrit-ui/app/elements/admin/gr-repo-access/gr-repo-access.js b/polygerrit-ui/app/elements/admin/gr-repo-access/gr-repo-access.js
index fc50677..57a9183 100644
--- a/polygerrit-ui/app/elements/admin/gr-repo-access/gr-repo-access.js
+++ b/polygerrit-ui/app/elements/admin/gr-repo-access/gr-repo-access.js
@@ -67,66 +67,77 @@
    */
   Defs.projectAccessInput;
 
-  Polymer({
-    is: 'gr-repo-access',
+  /**
+    * @appliesMixin Gerrit.AccessMixin
+    * @appliesMixin Gerrit.BaseUrlMixin
+    * @appliesMixin Gerrit.FireMixin
+    * @appliesMixin Gerrit.URLEncodingMixin
+    */
+  class GrRepoAccess extends Polymer.mixinBehaviors( [
+    Gerrit.AccessBehavior,
+    Gerrit.BaseUrlBehavior,
+    Gerrit.FireBehavior,
+    Gerrit.URLEncodingBehavior,
+  ], Polymer.GestureEventListeners(
+      Polymer.LegacyElementMixin(
+          Polymer.Element))) {
+    static get is() { return 'gr-repo-access'; }
 
-    properties: {
-      repo: {
-        type: String,
-        observer: '_repoChanged',
-      },
-      // The current path
-      path: String,
-
-      _canUpload: {
-        type: Boolean,
-        value: false,
-      },
-      _inheritFromFilter: String,
-      _query: {
-        type: Function,
-        value() {
-          return this._getInheritFromSuggestions.bind(this);
+    static get properties() {
+      return {
+        repo: {
+          type: String,
+          observer: '_repoChanged',
         },
-      },
-      _ownerOf: Array,
-      _capabilities: Object,
-      _groups: Object,
-      /** @type {?} */
-      _inheritsFrom: Object,
-      _labels: Object,
-      _local: Object,
-      _editing: {
-        type: Boolean,
-        value: false,
-        observer: '_handleEditingChanged',
-      },
-      _modified: {
-        type: Boolean,
-        value: false,
-      },
-      _sections: Array,
-      _weblinks: Array,
-      _loading: {
-        type: Boolean,
-        value: true,
-      },
-    },
+        // The current path
+        path: String,
 
-    behaviors: [
-      Gerrit.AccessBehavior,
-      Gerrit.BaseUrlBehavior,
-      Gerrit.FireBehavior,
-      Gerrit.URLEncodingBehavior,
-    ],
+        _canUpload: {
+          type: Boolean,
+          value: false,
+        },
+        _inheritFromFilter: String,
+        _query: {
+          type: Function,
+          value() {
+            return this._getInheritFromSuggestions.bind(this);
+          },
+        },
+        _ownerOf: Array,
+        _capabilities: Object,
+        _groups: Object,
+        /** @type {?} */
+        _inheritsFrom: Object,
+        _labels: Object,
+        _local: Object,
+        _editing: {
+          type: Boolean,
+          value: false,
+          observer: '_handleEditingChanged',
+        },
+        _modified: {
+          type: Boolean,
+          value: false,
+        },
+        _sections: Array,
+        _weblinks: Array,
+        _loading: {
+          type: Boolean,
+          value: true,
+        },
+      };
+    }
 
-    listeners: {
-      'access-modified': '_handleAccessModified',
-    },
+    created() {
+      super.created();
+      this.addEventListener('access-modified',
+          () =>
+            this._handleAccessModified());
+    }
 
     _handleAccessModified() {
       this._modified = true;
-    },
+    }
 
     /**
      * @param {string} repo
@@ -138,7 +149,7 @@
       if (!repo) { return Promise.resolve(); }
 
       return this._reload(repo);
-    },
+    }
 
     _reload(repo) {
       const promises = [];
@@ -195,7 +206,7 @@
         this._sections = sections;
         this._loading = false;
       });
-    },
+    }
 
     _handleUpdateInheritFrom(e) {
       if (!this._inheritsFrom) {
@@ -204,7 +215,7 @@
       this._inheritsFrom.id = e.detail.value;
       this._inheritsFrom.name = this._inheritFromFilter;
       this._handleAccessModified();
-    },
+    }
 
     _getInheritFromSuggestions() {
       return this.$.restAPI.getRepos(
@@ -221,33 +232,33 @@
             }
             return projects;
           });
-    },
+    }
 
     _computeLoadingClass(loading) {
       return loading ? 'loading' : '';
-    },
+    }
 
     _handleEdit() {
       this._editing = !this._editing;
-    },
+    }
 
     _editOrCancel(editing) {
       return editing ? 'Cancel' : 'Edit';
-    },
+    }
 
     _computeWebLinkClass(weblinks) {
       return weblinks && weblinks.length ? 'show' : '';
-    },
+    }
 
     _computeShowInherit(inheritsFrom) {
       return inheritsFrom ? 'show' : '';
-    },
+    }
 
     _handleAddedSectionRemoved(e) {
       const index = e.model.index;
       this._sections = this._sections.slice(0, index)
           .concat(this._sections.slice(index + 1, this._sections.length));
-    },
+    }
 
     _handleEditingChanged(editing, editingOld) {
       // Ignore when editing gets set initially.
@@ -266,7 +277,7 @@
           delete this._local[key];
         }
       }
-    },
+    }
 
     /**
      * @param {!Defs.projectAccessInput} addRemoveObj
@@ -296,7 +307,7 @@
         curPos = curPos[item];
       }
       return addRemoveObj;
-    },
+    }
 
     /**
      * Used to recursively remove any objects with a 'deleted' bit.
@@ -313,7 +324,7 @@
           this._recursivelyRemoveDeleted(obj[k]);
         }
       }
-    },
+    }
 
     _recursivelyUpdateAddRemoveObj(obj, addRemoveObj, path = []) {
       for (const k in obj) {
@@ -354,7 +365,7 @@
               path.concat(k));
         }
       }
-    },
+    }
 
     /**
      * Returns an object formatted for saving or submitting access changes for
@@ -388,7 +399,7 @@
         addRemoveObj.parent = inheritsFromId;
       }
       return addRemoveObj;
-    },
+    }
 
     _handleCreateSection() {
       let newRef = 'refs/for/*';
@@ -403,7 +414,7 @@
       Polymer.dom.flush();
       Polymer.dom(this.root).querySelector('gr-access-section:last-of-type')
           .editReference();
-    },
+    }
 
     _getObjforSave() {
       const addRemoveObj = this._computeAddAndRemove();
@@ -426,7 +437,7 @@
         obj.parent = addRemoveObj.parent;
       }
       return obj;
-    },
+    }
 
     _handleSave() {
       const obj = this._getObjforSave();
@@ -435,7 +446,7 @@
           .then(() => {
             this._reload(this.repo);
           });
-    },
+    }
 
     _handleSaveForReview() {
       const obj = this._getObjforSave();
@@ -444,15 +455,15 @@
           .then(change => {
             Gerrit.Nav.navigateToChange(change);
           });
-    },
+    }
 
     _computeSaveReviewBtnClass(canUpload) {
       return !canUpload ? 'invisible' : '';
-    },
+    }
 
     _computeSaveBtnClass(ownerOf) {
       return ownerOf && ownerOf.length === 0 ? 'invisible' : '';
-    },
+    }
 
     _computeMainClass(ownerOf, canUpload, editing) {
       const classList = [];
@@ -463,11 +474,13 @@
         classList.push('editing');
       }
       return classList.join(' ');
-    },
+    }
 
     _computeParentHref(repoName) {
       return this.getBaseUrl() +
           `/admin/repos/${this.encodeURL(repoName, true)},access`;
-    },
-  });
+    }
+  }
+
+  customElements.define(GrRepoAccess.is, GrRepoAccess);
 })();
diff --git a/polygerrit-ui/app/elements/admin/gr-repo-access/gr-repo-access_test.html b/polygerrit-ui/app/elements/admin/gr-repo-access/gr-repo-access_test.html
index 1660088..90eaba5 100644
--- a/polygerrit-ui/app/elements/admin/gr-repo-access/gr-repo-access_test.html
+++ b/polygerrit-ui/app/elements/admin/gr-repo-access/gr-repo-access_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-repo-access</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/page/page.js"></script>
diff --git a/polygerrit-ui/app/elements/admin/gr-repo-command/gr-repo-command.js b/polygerrit-ui/app/elements/admin/gr-repo-command/gr-repo-command.js
index 026c990..48e4c01 100644
--- a/polygerrit-ui/app/elements/admin/gr-repo-command/gr-repo-command.js
+++ b/polygerrit-ui/app/elements/admin/gr-repo-command/gr-repo-command.js
@@ -17,14 +17,18 @@
 (function() {
   'use strict';
 
-  Polymer({
-    is: 'gr-repo-command',
+  class GrRepoCommand extends Polymer.GestureEventListeners(
+      Polymer.LegacyElementMixin(
+          Polymer.Element)) {
+    static get is() { return 'gr-repo-command'; }
 
-    properties: {
-      title: String,
-      disabled: Boolean,
-      tooltip: String,
-    },
+    static get properties() {
+      return {
+        title: String,
+        disabled: Boolean,
+        tooltip: String,
+      };
+    }
 
     /**
      * Fired when command button is tapped.
@@ -35,6 +39,8 @@
     _onCommandTap() {
       this.dispatchEvent(
           new CustomEvent('command-tap', {bubbles: true, composed: true}));
-    },
-  });
+    }
+  }
+
+  customElements.define(GrRepoCommand.is, GrRepoCommand);
 })();
diff --git a/polygerrit-ui/app/elements/admin/gr-repo-command/gr-repo-command_test.html b/polygerrit-ui/app/elements/admin/gr-repo-command/gr-repo-command_test.html
index 49d8765..b8ec7e8 100644
--- a/polygerrit-ui/app/elements/admin/gr-repo-command/gr-repo-command_test.html
+++ b/polygerrit-ui/app/elements/admin/gr-repo-command/gr-repo-command_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-repo-command</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>
diff --git a/polygerrit-ui/app/elements/admin/gr-repo-commands/gr-repo-commands.js b/polygerrit-ui/app/elements/admin/gr-repo-commands/gr-repo-commands.js
index 95faaf8..f85af35 100644
--- a/polygerrit-ui/app/elements/admin/gr-repo-commands/gr-repo-commands.js
+++ b/polygerrit-ui/app/elements/admin/gr-repo-commands/gr-repo-commands.js
@@ -26,30 +26,36 @@
   const CREATE_CHANGE_FAILED_MESSAGE = 'Failed to create change.';
   const CREATE_CHANGE_SUCCEEDED_MESSAGE = 'Navigating to change';
 
-  Polymer({
-    is: 'gr-repo-commands',
+  /**
+    * @appliesMixin Gerrit.FireMixin
+    */
+  class GrRepoCommands extends Polymer.mixinBehaviors( [
+    Gerrit.FireBehavior,
+  ], Polymer.GestureEventListeners(
+      Polymer.LegacyElementMixin(
+          Polymer.Element))) {
+    static get is() { return 'gr-repo-commands'; }
 
-    properties: {
-      params: Object,
-      repo: String,
-      _loading: {
-        type: Boolean,
-        value: true,
-      },
-      /** @type {?} */
-      _repoConfig: Object,
-      _canCreate: Boolean,
-    },
-
-    behaviors: [
-      Gerrit.FireBehavior,
-    ],
+    static get properties() {
+      return {
+        params: Object,
+        repo: String,
+        _loading: {
+          type: Boolean,
+          value: true,
+        },
+        /** @type {?} */
+        _repoConfig: Object,
+        _canCreate: Boolean,
+      };
+    }
 
     attached() {
+      super.attached();
       this._loadRepo();
 
       this.fire('title-change', {title: 'Repo Commands'});
-    },
+    }
 
     _loadRepo() {
       if (!this.repo) { return Promise.resolve(); }
@@ -65,15 +71,15 @@
             this._repoConfig = config;
             this._loading = false;
           });
-    },
+    }
 
     _computeLoadingClass(loading) {
       return loading ? 'loading' : '';
-    },
+    }
 
     _isLoading() {
       return this._loading || this._loading === undefined;
-    },
+    }
 
     _handleRunningGC() {
       return this.$.restAPI.runRepoGC(this.repo).then(response => {
@@ -83,20 +89,20 @@
               {detail: {message: GC_MESSAGE}, bubbles: true, composed: true}));
         }
       });
-    },
+    }
 
     _createNewChange() {
       this.$.createChangeOverlay.open();
-    },
+    }
 
     _handleCreateChange() {
       this.$.createNewChangeModal.handleCreateChange();
       this._handleCloseCreateChange();
-    },
+    }
 
     _handleCloseCreateChange() {
       this.$.createChangeOverlay.close();
-    },
+    }
 
     _handleEditRepoConfig() {
       return this.$.restAPI.createChange(this.repo, CONFIG_BRANCH,
@@ -112,6 +118,8 @@
         Gerrit.Nav.navigateToRelativeUrl(Gerrit.Nav.getEditUrlForDiff(
             change, CONFIG_PATH, INITIAL_PATCHSET));
       });
-    },
-  });
+    }
+  }
+
+  customElements.define(GrRepoCommands.is, GrRepoCommands);
 })();
diff --git a/polygerrit-ui/app/elements/admin/gr-repo-commands/gr-repo-commands_test.html b/polygerrit-ui/app/elements/admin/gr-repo-commands/gr-repo-commands_test.html
index 2976923..3c19175 100644
--- a/polygerrit-ui/app/elements/admin/gr-repo-commands/gr-repo-commands_test.html
+++ b/polygerrit-ui/app/elements/admin/gr-repo-commands/gr-repo-commands_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-repo-commands</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/page/page.js"></script>
diff --git a/polygerrit-ui/app/elements/admin/gr-repo-dashboards/gr-repo-dashboards.js b/polygerrit-ui/app/elements/admin/gr-repo-dashboards/gr-repo-dashboards.js
index 71cc571..b4d34ae 100644
--- a/polygerrit-ui/app/elements/admin/gr-repo-dashboards/gr-repo-dashboards.js
+++ b/polygerrit-ui/app/elements/admin/gr-repo-dashboards/gr-repo-dashboards.js
@@ -17,24 +17,29 @@
 (function() {
   'use strict';
 
-  Polymer({
-    is: 'gr-repo-dashboards',
+  /**
+    * @appliesMixin Gerrit.FireMixin
+    */
+  class GrRepoDashboards extends Polymer.mixinBehaviors( [
+    Gerrit.FireBehavior,
+  ], Polymer.GestureEventListeners(
+      Polymer.LegacyElementMixin(
+          Polymer.Element))) {
+    static get is() { return 'gr-repo-dashboards'; }
 
-    properties: {
-      repo: {
-        type: String,
-        observer: '_repoChanged',
-      },
-      _loading: {
-        type: Boolean,
-        value: true,
-      },
-      _dashboards: Array,
-    },
-
-    behaviors: [
-      Gerrit.FireBehavior,
-    ],
+    static get properties() {
+      return {
+        repo: {
+          type: String,
+          observer: '_repoChanged',
+        },
+        _loading: {
+          type: Boolean,
+          value: true,
+        },
+        _dashboards: Array,
+      };
+    }
 
     _repoChanged(repo) {
       this._loading = true;
@@ -70,24 +75,26 @@
         this._loading = false;
         Polymer.dom.flush();
       });
-    },
+    }
 
     _getUrl(project, id) {
       if (!project || !id) { return ''; }
 
       return Gerrit.Nav.getUrlForRepoDashboard(project, id);
-    },
+    }
 
     _computeLoadingClass(loading) {
       return loading ? 'loading' : '';
-    },
+    }
 
     _computeInheritedFrom(project, definingProject) {
       return project === definingProject ? '' : definingProject;
-    },
+    }
 
     _computeIsDefault(isDefault) {
       return isDefault ? '✓' : '';
-    },
-  });
+    }
+  }
+
+  customElements.define(GrRepoDashboards.is, GrRepoDashboards);
 })();
diff --git a/polygerrit-ui/app/elements/admin/gr-repo-dashboards/gr-repo-dashboards_test.html b/polygerrit-ui/app/elements/admin/gr-repo-dashboards/gr-repo-dashboards_test.html
index 4f76983..06496de 100644
--- a/polygerrit-ui/app/elements/admin/gr-repo-dashboards/gr-repo-dashboards_test.html
+++ b/polygerrit-ui/app/elements/admin/gr-repo-dashboards/gr-repo-dashboards_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-repo-dashboards</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>
diff --git a/polygerrit-ui/app/elements/admin/gr-repo-detail-list/gr-repo-detail-list.js b/polygerrit-ui/app/elements/admin/gr-repo-detail-list/gr-repo-detail-list.js
index 9052322..bacc1e1 100644
--- a/polygerrit-ui/app/elements/admin/gr-repo-detail-list/gr-repo-detail-list.js
+++ b/polygerrit-ui/app/elements/admin/gr-repo-detail-list/gr-repo-detail-list.js
@@ -24,75 +24,82 @@
 
   const PGP_START = '-----BEGIN PGP SIGNATURE-----';
 
-  Polymer({
-    is: 'gr-repo-detail-list',
+  /**
+    * @appliesMixin Gerrit.ListViewMixin
+    * @appliesMixin Gerrit.FireMixin
+    * @appliesMixin Gerrit.URLEncodingMixin
+    */
+  class GrRepoDetailList extends Polymer.mixinBehaviors( [
+    Gerrit.ListViewBehavior,
+    Gerrit.FireBehavior,
+    Gerrit.URLEncodingBehavior,
+  ], Polymer.GestureEventListeners(
+      Polymer.LegacyElementMixin(
+          Polymer.Element))) {
+    static get is() { return 'gr-repo-detail-list'; }
 
-    properties: {
+    static get properties() {
+      return {
       /**
        * URL params passed from the router.
        */
-      params: {
-        type: Object,
-        observer: '_paramsChanged',
-      },
-      /**
+        params: {
+          type: Object,
+          observer: '_paramsChanged',
+        },
+        /**
        * The kind of detail we are displaying, possibilities are determined by
        * the const DETAIL_TYPES.
        */
-      detailType: String,
+        detailType: String,
 
-      _editing: {
-        type: Boolean,
-        value: false,
-      },
-      _isOwner: {
-        type: Boolean,
-        value: false,
-      },
-      _loggedIn: {
-        type: Boolean,
-        value: false,
-      },
-      /**
+        _editing: {
+          type: Boolean,
+          value: false,
+        },
+        _isOwner: {
+          type: Boolean,
+          value: false,
+        },
+        _loggedIn: {
+          type: Boolean,
+          value: false,
+        },
+        /**
        * Offset of currently visible query results.
        */
-      _offset: Number,
-      _repo: Object,
-      _items: Array,
-      /**
+        _offset: Number,
+        _repo: Object,
+        _items: Array,
+        /**
        * Because  we request one more than the projectsPerPage, _shownProjects
        * maybe one less than _projects.
        */
-      _shownItems: {
-        type: Array,
-        computed: 'computeShownItems(_items)',
-      },
-      _itemsPerPage: {
-        type: Number,
-        value: 25,
-      },
-      _loading: {
-        type: Boolean,
-        value: true,
-      },
-      _filter: String,
-      _refName: String,
-      _hasNewItemName: Boolean,
-      _isEditing: Boolean,
-      _revisedRef: String,
-    },
-
-    behaviors: [
-      Gerrit.ListViewBehavior,
-      Gerrit.FireBehavior,
-      Gerrit.URLEncodingBehavior,
-    ],
+        _shownItems: {
+          type: Array,
+          computed: 'computeShownItems(_items)',
+        },
+        _itemsPerPage: {
+          type: Number,
+          value: 25,
+        },
+        _loading: {
+          type: Boolean,
+          value: true,
+        },
+        _filter: String,
+        _refName: String,
+        _hasNewItemName: Boolean,
+        _isEditing: Boolean,
+        _revisedRef: String,
+      };
+    }
 
     _determineIfOwner(repo) {
       return this.$.restAPI.getRepoAccess(repo)
           .then(access =>
             this._isOwner = access && !!access[repo].is_owner);
-    },
+    }
 
     _paramsChanged(params) {
       if (!params || !params.repo) { return; }
@@ -113,7 +120,7 @@
 
       return this._getItems(this._filter, this._repo,
           this._itemsPerPage, this._offset, this.detailType);
-    },
+    }
 
     _getItems(filter, repo, itemsPerPage, offset, detailType) {
       this._loading = true;
@@ -137,24 +144,24 @@
           this._loading = false;
         });
       }
-    },
+    }
 
     _getPath(repo) {
       return `/admin/repos/${this.encodeURL(repo, false)},` +
           `${this.detailType}`;
-    },
+    }
 
     _computeWeblink(repo) {
       if (!repo.web_links) { return ''; }
       const webLinks = repo.web_links;
       return webLinks.length ? webLinks : null;
-    },
+    }
 
     _computeMessage(message) {
       if (!message) { return; }
       // Strip PGP info.
       return message.split(PGP_START)[0];
-    },
+    }
 
     _stripRefs(item, detailType) {
       if (detailType === DETAIL_TYPES.BRANCHES) {
@@ -162,33 +169,33 @@
       } else if (detailType === DETAIL_TYPES.TAGS) {
         return item.replace('refs/tags/', '');
       }
-    },
+    }
 
     _getLoggedIn() {
       return this.$.restAPI.getLoggedIn();
-    },
+    }
 
     _computeEditingClass(isEditing) {
       return isEditing ? 'editing' : '';
-    },
+    }
 
     _computeCanEditClass(ref, detailType, isOwner) {
       return isOwner && this._stripRefs(ref, detailType) === 'HEAD' ?
         'canEdit' : '';
-    },
+    }
 
     _handleEditRevision(e) {
       this._revisedRef = e.model.get('item.revision');
       this._isEditing = true;
-    },
+    }
 
     _handleCancelRevision() {
       this._isEditing = false;
-    },
+    }
 
     _handleSaveRevision(e) {
       this._setRepoHead(this._repo, this._revisedRef, e);
-    },
+    }
 
     _setRepoHead(repo, ref, e) {
       return this.$.restAPI.setRepoHead(repo, ref).then(res => {
@@ -202,7 +209,7 @@
               this._offset, this.detailType);
         }
       });
-    },
+    }
 
     _computeItemName(detailType) {
       if (detailType === DETAIL_TYPES.BRANCHES) {
@@ -210,7 +217,7 @@
       } else if (detailType === DETAIL_TYPES.TAGS) {
         return 'Tag';
       }
-    },
+    }
 
     _handleDeleteItemConfirm() {
       this.$.overlay.close();
@@ -233,18 +240,18 @@
               }
             });
       }
-    },
+    }
 
     _handleConfirmDialogCancel() {
       this.$.overlay.close();
-    },
+    }
 
     _handleDeleteItem(e) {
       const name = this._stripRefs(e.model.get('item.ref'), this.detailType);
       if (!name) { return; }
       this._refName = name;
       this.$.overlay.open();
-    },
+    }
 
     _computeHideDeleteClass(owner, canDelete) {
       if (canDelete || owner) {
@@ -252,20 +259,20 @@
       }
 
       return '';
-    },
+    }
 
     _handleCreateItem() {
       this.$.createNewModal.handleCreateItem();
       this._handleCloseCreate();
-    },
+    }
 
     _handleCloseCreate() {
       this.$.createOverlay.close();
-    },
+    }
 
     _handleCreateClicked() {
       this.$.createOverlay.open();
-    },
+    }
 
     _hideIfBranch(type) {
       if (type === DETAIL_TYPES.BRANCHES) {
@@ -273,10 +280,12 @@
       }
 
       return '';
-    },
+    }
 
     _computeHideTagger(tagger) {
       return tagger ? '' : 'hide';
-    },
-  });
+    }
+  }
+
+  customElements.define(GrRepoDetailList.is, GrRepoDetailList);
 })();
diff --git a/polygerrit-ui/app/elements/admin/gr-repo-detail-list/gr-repo-detail-list_test.html b/polygerrit-ui/app/elements/admin/gr-repo-detail-list/gr-repo-detail-list_test.html
index 44d9b27..2b175c7 100644
--- a/polygerrit-ui/app/elements/admin/gr-repo-detail-list/gr-repo-detail-list_test.html
+++ b/polygerrit-ui/app/elements/admin/gr-repo-detail-list/gr-repo-detail-list_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-repo-detail-list</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/page/page.js"></script>
diff --git a/polygerrit-ui/app/elements/admin/gr-repo-list/gr-repo-list.js b/polygerrit-ui/app/elements/admin/gr-repo-list/gr-repo-list.js
index e0b054b..82bb3e2 100644
--- a/polygerrit-ui/app/elements/admin/gr-repo-list/gr-repo-list.js
+++ b/polygerrit-ui/app/elements/admin/gr-repo-list/gr-repo-list.js
@@ -17,67 +17,73 @@
 (function() {
   'use strict';
 
-  Polymer({
-    is: 'gr-repo-list',
+  /**
+    * @appliesMixin Gerrit.ListViewMixin
+    */
+  class GrRepoList extends Polymer.mixinBehaviors( [
+    Gerrit.ListViewBehavior,
+  ], Polymer.GestureEventListeners(
+      Polymer.LegacyElementMixin(
+          Polymer.Element))) {
+    static get is() { return 'gr-repo-list'; }
 
-    properties: {
+    static get properties() {
+      return {
       /**
        * URL params passed from the router.
        */
-      params: {
-        type: Object,
-        observer: '_paramsChanged',
-      },
+        params: {
+          type: Object,
+          observer: '_paramsChanged',
+        },
 
-      /**
+        /**
        * Offset of currently visible query results.
        */
-      _offset: Number,
-      _path: {
-        type: String,
-        readOnly: true,
-        value: '/admin/repos',
-      },
-      _hasNewRepoName: Boolean,
-      _createNewCapability: {
-        type: Boolean,
-        value: false,
-      },
-      _repos: Array,
+        _offset: Number,
+        _path: {
+          type: String,
+          readOnly: true,
+          value: '/admin/repos',
+        },
+        _hasNewRepoName: Boolean,
+        _createNewCapability: {
+          type: Boolean,
+          value: false,
+        },
+        _repos: Array,
 
-      /**
+        /**
        * Because  we request one more than the projectsPerPage, _shownProjects
        * maybe one less than _projects.
        * */
-      _shownRepos: {
-        type: Array,
-        computed: 'computeShownItems(_repos)',
-      },
+        _shownRepos: {
+          type: Array,
+          computed: 'computeShownItems(_repos)',
+        },
 
-      _reposPerPage: {
-        type: Number,
-        value: 25,
-      },
+        _reposPerPage: {
+          type: Number,
+          value: 25,
+        },
 
-      _loading: {
-        type: Boolean,
-        value: true,
-      },
-      _filter: {
-        type: String,
-        value: '',
-      },
-    },
-
-    behaviors: [
-      Gerrit.ListViewBehavior,
-    ],
+        _loading: {
+          type: Boolean,
+          value: true,
+        },
+        _filter: {
+          type: String,
+          value: '',
+        },
+      };
+    }
 
     attached() {
+      super.attached();
       this._getCreateRepoCapability();
       this.fire('title-change', {title: 'Repos'});
       this._maybeOpenCreateOverlay(this.params);
-    },
+    }
 
     _paramsChanged(params) {
       this._loading = true;
@@ -86,7 +92,7 @@
 
       return this._getRepos(this._filter, this._reposPerPage,
           this._offset);
-    },
+    }
 
     /**
      * Opens the create overlay if the route has a hash 'create'
@@ -96,15 +102,15 @@
       if (params && params.openCreateModal) {
         this.$.createOverlay.open();
       }
-    },
+    }
 
     _computeRepoUrl(name) {
       return this.getUrl(this._path + '/', name);
-    },
+    }
 
     _computeChangesLink(name) {
       return Gerrit.Nav.getUrlForProjectChanges(name);
-    },
+    }
 
     _getCreateRepoCapability() {
       return this.$.restAPI.getAccount().then(account => {
@@ -116,7 +122,7 @@
               }
             });
       });
-    },
+    }
 
     _getRepos(filter, reposPerPage, offset) {
       this._repos = [];
@@ -127,36 +133,38 @@
             this._repos = repos;
             this._loading = false;
           });
-    },
+    }
 
     _refreshReposList() {
       this.$.restAPI.invalidateReposCache();
       return this._getRepos(this._filter, this._reposPerPage,
           this._offset);
-    },
+    }
 
     _handleCreateRepo() {
       this.$.createNewModal.handleCreateRepo().then(() => {
         this._refreshReposList();
       });
-    },
+    }
 
     _handleCloseCreate() {
       this.$.createOverlay.close();
-    },
+    }
 
     _handleCreateClicked() {
       this.$.createOverlay.open();
-    },
+    }
 
     _readOnly(item) {
       return item.state === 'READ_ONLY' ? 'Y' : '';
-    },
+    }
 
     _computeWeblink(repo) {
       if (!repo.web_links) { return ''; }
       const webLinks = repo.web_links;
       return webLinks.length ? webLinks : null;
-    },
-  });
+    }
+  }
+
+  customElements.define(GrRepoList.is, GrRepoList);
 })();
diff --git a/polygerrit-ui/app/elements/admin/gr-repo-list/gr-repo-list_test.html b/polygerrit-ui/app/elements/admin/gr-repo-list/gr-repo-list_test.html
index c77592c..9569e56 100644
--- a/polygerrit-ui/app/elements/admin/gr-repo-list/gr-repo-list_test.html
+++ b/polygerrit-ui/app/elements/admin/gr-repo-list/gr-repo-list_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-repo-list</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/page/page.js"></script>
diff --git a/polygerrit-ui/app/elements/admin/gr-repo-plugin-config/gr-repo-plugin-config.js b/polygerrit-ui/app/elements/admin/gr-repo-plugin-config/gr-repo-plugin-config.js
index 883a4e1..8658397 100644
--- a/polygerrit-ui/app/elements/admin/gr-repo-plugin-config/gr-repo-plugin-config.js
+++ b/polygerrit-ui/app/elements/admin/gr-repo-plugin-config/gr-repo-plugin-config.js
@@ -17,28 +17,32 @@
 (function() {
   'use strict';
 
-  Polymer({
-    is: 'gr-repo-plugin-config',
-
+  /**
+    * @appliesMixin Gerrit.RepoPluginConfigMixin
+    */
+  class GrRepoPluginConfig extends Polymer.mixinBehaviors( [
+    Gerrit.RepoPluginConfig,
+  ], Polymer.GestureEventListeners(
+      Polymer.LegacyElementMixin(
+          Polymer.Element))) {
+    static get is() { return 'gr-repo-plugin-config'; }
     /**
      * Fired when the plugin config changes.
      *
      * @event plugin-config-changed
      */
 
-    properties: {
+    static get properties() {
+      return {
       /** @type {?} */
-      pluginData: Object,
-      /** @type {Array} */
-      _pluginConfigOptions: {
-        type: Array,
-        computed: '_computePluginConfigOptions(pluginData.*)',
-      },
-    },
-
-    behaviors: [
-      Gerrit.RepoPluginConfig,
-    ],
+        pluginData: Object,
+        /** @type {Array} */
+        _pluginConfigOptions: {
+          type: Array,
+          computed: '_computePluginConfigOptions(pluginData.*)',
+        },
+      };
+    }
 
     _computePluginConfigOptions(dataRecord) {
       if (!dataRecord || !dataRecord.base || !dataRecord.base.config) {
@@ -46,34 +50,34 @@
       }
       const {config} = dataRecord.base;
       return Object.keys(config).map(_key => ({_key, info: config[_key]}));
-    },
+    }
 
     _isArray(type) {
       return type === this.ENTRY_TYPES.ARRAY;
-    },
+    }
 
     _isBoolean(type) {
       return type === this.ENTRY_TYPES.BOOLEAN;
-    },
+    }
 
     _isList(type) {
       return type === this.ENTRY_TYPES.LIST;
-    },
+    }
 
     _isString(type) {
       // Treat numbers like strings for simplicity.
       return type === this.ENTRY_TYPES.STRING ||
           type === this.ENTRY_TYPES.INT ||
           type === this.ENTRY_TYPES.LONG;
-    },
+    }
 
     _computeDisabled(editable) {
       return editable === 'false';
-    },
+    }
 
     _computeChecked(value) {
       return JSON.parse(value);
-    },
+    }
 
     _handleStringChange(e) {
       const el = Polymer.dom(e).localTarget;
@@ -81,7 +85,7 @@
       const configChangeInfo =
           this._buildConfigChangeInfo(el.value, _key);
       this._handleChange(configChangeInfo);
-    },
+    }
 
     _handleListChange(e) {
       const el = Polymer.dom(e).localTarget;
@@ -89,7 +93,7 @@
       const configChangeInfo =
           this._buildConfigChangeInfo(el.value, _key);
       this._handleChange(configChangeInfo);
-    },
+    }
 
     _handleBooleanChange(e) {
       const el = Polymer.dom(e).localTarget;
@@ -97,7 +101,7 @@
       const configChangeInfo =
           this._buildConfigChangeInfo(JSON.stringify(el.checked), _key);
       this._handleChange(configChangeInfo);
-    },
+    }
 
     _buildConfigChangeInfo(value, _key) {
       const info = this.pluginData.config[_key];
@@ -107,11 +111,11 @@
         info,
         notifyPath: `${_key}.value`,
       };
-    },
+    }
 
     _handleArrayChange({detail}) {
       this._handleChange(detail);
-    },
+    }
 
     _handleChange({_key, info, notifyPath}) {
       const {name, config} = this.pluginData;
@@ -125,6 +129,8 @@
 
       this.dispatchEvent(new CustomEvent(
           this.PLUGIN_CONFIG_CHANGED, {detail, bubbles: true, composed: true}));
-    },
-  });
+    }
+  }
+
+  customElements.define(GrRepoPluginConfig.is, GrRepoPluginConfig);
 })();
diff --git a/polygerrit-ui/app/elements/admin/gr-repo-plugin-config/gr-repo-plugin-config_test.html b/polygerrit-ui/app/elements/admin/gr-repo-plugin-config/gr-repo-plugin-config_test.html
index 0a6846f..ff0e522 100644
--- a/polygerrit-ui/app/elements/admin/gr-repo-plugin-config/gr-repo-plugin-config_test.html
+++ b/polygerrit-ui/app/elements/admin/gr-repo-plugin-config/gr-repo-plugin-config_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-repo-plugin-config</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>
diff --git a/polygerrit-ui/app/elements/admin/gr-repo/gr-repo.js b/polygerrit-ui/app/elements/admin/gr-repo/gr-repo.js
index 153a137..5bd7d27a 100644
--- a/polygerrit-ui/app/elements/admin/gr-repo/gr-repo.js
+++ b/polygerrit-ui/app/elements/admin/gr-repo/gr-repo.js
@@ -51,76 +51,84 @@
     },
   };
 
-  Polymer({
-    is: 'gr-repo',
+  /**
+    * @appliesMixin Gerrit.FireMixin
+    */
+  class GrRepo extends Polymer.mixinBehaviors( [
+    Gerrit.FireBehavior,
+  ], Polymer.GestureEventListeners(
+      Polymer.LegacyElementMixin(
+          Polymer.Element))) {
+    static get is() { return 'gr-repo'; }
 
-    properties: {
-      params: Object,
-      repo: String,
+    static get properties() {
+      return {
+        params: Object,
+        repo: String,
 
-      _configChanged: {
-        type: Boolean,
-        value: false,
-      },
-      _loading: {
-        type: Boolean,
-        value: true,
-      },
-      _loggedIn: {
-        type: Boolean,
-        value: false,
-        observer: '_loggedInChanged',
-      },
-      /** @type {?} */
-      _repoConfig: Object,
-      /** @type {?} */
-      _pluginData: {
-        type: Array,
-        computed: '_computePluginData(_repoConfig.plugin_config.*)',
-      },
-      _readOnly: {
-        type: Boolean,
-        value: true,
-      },
-      _states: {
-        type: Array,
-        value() {
-          return Object.values(STATES);
+        _configChanged: {
+          type: Boolean,
+          value: false,
         },
-      },
-      _submitTypes: {
-        type: Array,
-        value() {
-          return Object.values(SUBMIT_TYPES);
+        _loading: {
+          type: Boolean,
+          value: true,
         },
-      },
-      _schemes: {
-        type: Array,
-        value() { return []; },
-        computed: '_computeSchemes(_schemesObj)',
-        observer: '_schemesChanged',
-      },
-      _selectedCommand: {
-        type: String,
-        value: 'Clone',
-      },
-      _selectedScheme: String,
-      _schemesObj: Object,
-    },
+        _loggedIn: {
+          type: Boolean,
+          value: false,
+          observer: '_loggedInChanged',
+        },
+        /** @type {?} */
+        _repoConfig: Object,
+        /** @type {?} */
+        _pluginData: {
+          type: Array,
+          computed: '_computePluginData(_repoConfig.plugin_config.*)',
+        },
+        _readOnly: {
+          type: Boolean,
+          value: true,
+        },
+        _states: {
+          type: Array,
+          value() {
+            return Object.values(STATES);
+          },
+        },
+        _submitTypes: {
+          type: Array,
+          value() {
+            return Object.values(SUBMIT_TYPES);
+          },
+        },
+        _schemes: {
+          type: Array,
+          value() { return []; },
+          computed: '_computeSchemes(_schemesObj)',
+          observer: '_schemesChanged',
+        },
+        _selectedCommand: {
+          type: String,
+          value: 'Clone',
+        },
+        _selectedScheme: String,
+        _schemesObj: Object,
+      };
+    }
 
-    behaviors: [
-      Gerrit.FireBehavior,
-    ],
-
-    observers: [
-      '_handleConfigChanged(_repoConfig.*)',
-    ],
+    static get observers() {
+      return [
+        '_handleConfigChanged(_repoConfig.*)',
+      ];
+    }
 
     attached() {
+      super.attached();
       this._loadRepo();
 
       this.fire('title-change', {title: this.repo});
-    },
+    }
 
     _computePluginData(configRecord) {
       if (!configRecord ||
@@ -129,7 +137,7 @@
       const pluginConfig = configRecord.base;
       return Object.keys(pluginConfig)
           .map(name => ({name, config: pluginConfig[name]}));
-    },
+    }
 
     _loadRepo() {
       if (!this.repo) { return Promise.resolve(); }
@@ -179,15 +187,15 @@
       }));
 
       return Promise.all(promises);
-    },
+    }
 
     _computeLoadingClass(loading) {
       return loading ? 'loading' : '';
-    },
+    }
 
     _computeHideClass(arr) {
       return !arr || !arr.length ? 'hide' : '';
-    },
+    }
 
     _loggedInChanged(_loggedIn) {
       if (!_loggedIn) { return; }
@@ -197,7 +205,7 @@
           this._selectedScheme = prefs.download_scheme.toLowerCase();
         }
       });
-    },
+    }
 
     _formatBooleanSelect(item) {
       if (!item) { return; }
@@ -218,7 +226,7 @@
           value: 'FALSE',
         },
       ];
-    },
+    }
 
     _formatSubmitTypeSelect(projectConfig) {
       if (!projectConfig) { return; }
@@ -248,15 +256,15 @@
         },
         ...allValues,
       ];
-    },
+    }
 
     _isLoading() {
       return this._loading || this._loading === undefined;
-    },
+    }
 
     _getLoggedIn() {
       return this.$.restAPI.getLoggedIn();
-    },
+    }
 
     _formatRepoConfigForSave(repoConfig) {
       const configInputObj = {};
@@ -278,38 +286,38 @@
         }
       }
       return configInputObj;
-    },
+    }
 
     _handleSaveRepoConfig() {
       return this.$.restAPI.saveRepoConfig(this.repo,
           this._formatRepoConfigForSave(this._repoConfig)).then(() => {
         this._configChanged = false;
       });
-    },
+    }
 
     _handleConfigChanged() {
       if (this._isLoading()) { return; }
       this._configChanged = true;
-    },
+    }
 
     _computeButtonDisabled(readOnly, configChanged) {
       return readOnly || !configChanged;
-    },
+    }
 
     _computeHeaderClass(configChanged) {
       return configChanged ? 'edited' : '';
-    },
+    }
 
     _computeSchemes(schemesObj) {
       return Object.keys(schemesObj);
-    },
+    }
 
     _schemesChanged(schemes) {
       if (schemes.length === 0) { return; }
       if (!schemes.includes(this._selectedScheme)) {
         this._selectedScheme = schemes.sort()[0];
       }
-    },
+    }
 
     _computeCommands(repo, schemesObj, _selectedScheme) {
       if (!schemesObj || !repo || !_selectedScheme) {
@@ -331,19 +339,21 @@
         });
       }
       return commands;
-    },
+    }
 
     _computeRepositoriesClass(config) {
       return config ? 'showConfig': '';
-    },
+    }
 
     _computeChangesUrl(name) {
       return Gerrit.Nav.getUrlForProjectChanges(name);
-    },
+    }
 
     _handlePluginConfigChanged({detail: {name, config, notifyPath}}) {
       this._repoConfig.plugin_config[name] = config;
       this.notifyPath('_repoConfig.plugin_config.' + notifyPath);
-    },
-  });
+    }
+  }
+
+  customElements.define(GrRepo.is, GrRepo);
 })();
diff --git a/polygerrit-ui/app/elements/admin/gr-repo/gr-repo_test.html b/polygerrit-ui/app/elements/admin/gr-repo/gr-repo_test.html
index 4e81565..102a45e 100644
--- a/polygerrit-ui/app/elements/admin/gr-repo/gr-repo_test.html
+++ b/polygerrit-ui/app/elements/admin/gr-repo/gr-repo_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-repo</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>
diff --git a/polygerrit-ui/app/elements/admin/gr-rule-editor/gr-rule-editor.js b/polygerrit-ui/app/elements/admin/gr-rule-editor/gr-rule-editor.js
index df5a9f3..7c00787 100644
--- a/polygerrit-ui/app/elements/admin/gr-rule-editor/gr-rule-editor.js
+++ b/polygerrit-ui/app/elements/admin/gr-rule-editor/gr-rule-editor.js
@@ -63,62 +63,76 @@
     },
   ];
 
-  Polymer({
-    is: 'gr-rule-editor',
-
-    properties: {
-      hasRange: Boolean,
-      /** @type {?} */
-      label: Object,
-      editing: {
-        type: Boolean,
-        value: false,
-        observer: '_handleEditingChanged',
-      },
-      groupId: String,
-      groupName: String,
-      permission: String,
-      /** @type {?} */
-      rule: {
-        type: Object,
-        notify: true,
-      },
-      section: String,
-
-      _deleted: {
-        type: Boolean,
-        value: false,
-      },
-      _originalRuleValues: Object,
-    },
-
-    behaviors: [
-      Gerrit.AccessBehavior,
-      Gerrit.BaseUrlBehavior,
-      /**
+  /**
+    * @appliesMixin Gerrit.AccessMixin
+    * @appliesMixin Gerrit.BaseUrlMixin
+    * @appliesMixin Gerrit.FireMixin
+    * @appliesMixin Gerrit.URLEncodingMixin
+    */
+  class GrRuleEditor extends Polymer.mixinBehaviors( [
+    Gerrit.AccessBehavior,
+    Gerrit.BaseUrlBehavior,
+    /**
        * Unused in this element, but called by other elements in tests
        * e.g gr-permission_test.
        */
-      Gerrit.FireBehavior,
-      Gerrit.URLEncodingBehavior,
-    ],
+    Gerrit.FireBehavior,
+    Gerrit.URLEncodingBehavior,
+  ], Polymer.GestureEventListeners(
+      Polymer.LegacyElementMixin(
+          Polymer.Element))) {
+    static get is() { return 'gr-rule-editor'; }
 
-    observers: [
-      '_handleValueChange(rule.value.*)',
-    ],
+    static get properties() {
+      return {
+        hasRange: Boolean,
+        /** @type {?} */
+        label: Object,
+        editing: {
+          type: Boolean,
+          value: false,
+          observer: '_handleEditingChanged',
+        },
+        groupId: String,
+        groupName: String,
+        permission: String,
+        /** @type {?} */
+        rule: {
+          type: Object,
+          notify: true,
+        },
+        section: String,
 
-    listeners: {
-      'access-saved': '_handleAccessSaved',
-    },
+        _deleted: {
+          type: Boolean,
+          value: false,
+        },
+        _originalRuleValues: Object,
+      };
+    }
+
+    static get observers() {
+      return [
+        '_handleValueChange(rule.value.*)',
+      ];
+    }
+
+    created() {
+      super.created();
+      this.addEventListener('access-saved',
+          () => this._handleAccessSaved());
+    }
 
     ready() {
+      super.ready();
       // Called on ready rather than the observer because when new rules are
       // added, the observer is triggered prior to being ready.
       if (!this.rule) { return; } // Check needed for test purposes.
       this._setupValues(this.rule);
-    },
+    }
 
     attached() {
+      super.attached();
       if (!this.rule) { return; } // Check needed for test purposes.
       if (!this._originalRuleValues) {
         // Observer _handleValueChange is called after the ready()
@@ -126,13 +140,13 @@
         // avoid set .modified flag to true
         this._setOriginalRuleValues(this.rule.value);
       }
-    },
+    }
 
     _setupValues(rule) {
       if (!rule.value) {
         this._setDefaultRuleValues();
       }
-    },
+    }
 
     _computeForce(permission, action) {
       if (this.permissionValues.push.id === permission &&
@@ -141,21 +155,21 @@
       }
 
       return this.permissionValues.editTopicName.id === permission;
-    },
+    }
 
     _computeForceClass(permission, action) {
       return this._computeForce(permission, action) ? 'force' : '';
-    },
+    }
 
     _computeGroupPath(group) {
       return `${this.getBaseUrl()}/admin/groups/${this.encodeURL(group, true)}`;
-    },
+    }
 
     _handleAccessSaved() {
       // Set a new 'original' value to keep track of after the value has been
       // saved.
       this._setOriginalRuleValues(this.rule.value);
-    },
+    }
 
     _handleEditingChanged(editing, editingOld) {
       // Ignore when editing gets set initially.
@@ -164,7 +178,7 @@
       if (!editing) {
         this._handleUndoChange();
       }
-    },
+    }
 
     _computeSectionClass(editing, deleted) {
       const classList = [];
@@ -175,7 +189,7 @@
         classList.push('deleted');
       }
       return classList.join(' ');
-    },
+    }
 
     _computeForceOptions(permission, action) {
       if (permission === this.permissionValues.push.id) {
@@ -190,7 +204,7 @@
         return FORCE_EDIT_OPTIONS;
       }
       return [];
-    },
+    }
 
     _getDefaultRuleValues(permission, label) {
       const ruleAction = Action.ALLOW;
@@ -207,19 +221,19 @@
       }
       value.action = DROPDOWN_OPTIONS[0];
       return value;
-    },
+    }
 
     _setDefaultRuleValues() {
       this.set('rule.value', this._getDefaultRuleValues(this.permission,
           this.label));
-    },
+    }
 
     _computeOptions(permission) {
       if (permission === 'priority') {
         return PRIORITY_OPTIONS;
       }
       return DROPDOWN_OPTIONS;
-    },
+    }
 
     _handleRemoveRule() {
       if (this.rule.value.added) {
@@ -230,12 +244,12 @@
       this.rule.value.deleted = true;
       this.dispatchEvent(
           new CustomEvent('access-modified', {bubbles: true, composed: true}));
-    },
+    }
 
     _handleUndoRemove() {
       this._deleted = false;
       delete this.rule.value.deleted;
-    },
+    }
 
     _handleUndoChange() {
       // gr-permission will take care of removing rules that were added but
@@ -245,7 +259,7 @@
       this._deleted = false;
       delete this.rule.value.deleted;
       delete this.rule.value.modified;
-    },
+    }
 
     _handleValueChange() {
       if (!this._originalRuleValues) { return; }
@@ -253,10 +267,12 @@
       // Allows overall access page to know a change has been made.
       this.dispatchEvent(
           new CustomEvent('access-modified', {bubbles: true, composed: true}));
-    },
+    }
 
     _setOriginalRuleValues(value) {
       this._originalRuleValues = Object.assign({}, value);
-    },
-  });
+    }
+  }
+
+  customElements.define(GrRuleEditor.is, GrRuleEditor);
 })();
diff --git a/polygerrit-ui/app/elements/admin/gr-rule-editor/gr-rule-editor_test.html b/polygerrit-ui/app/elements/admin/gr-rule-editor/gr-rule-editor_test.html
index 6d533af..dbf5bb0 100644
--- a/polygerrit-ui/app/elements/admin/gr-rule-editor/gr-rule-editor_test.html
+++ b/polygerrit-ui/app/elements/admin/gr-rule-editor/gr-rule-editor_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-rule-editor</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/page/page.js"></script>
diff --git a/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item.js b/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item.js
index 8eb39891..3ce87f8 100644
--- a/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item.js
+++ b/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item.js
@@ -24,57 +24,67 @@
     LARGE: 1000,
   };
 
-  Polymer({
-    is: 'gr-change-list-item',
+  /**
+    * @appliesMixin Gerrit.BaseUrlMixin
+    * @appliesMixin Gerrit.ChangeTableMixin
+    * @appliesMixin Gerrit.PathListMixin
+    * @appliesMixin Gerrit.RESTClientMixin
+    * @appliesMixin Gerrit.URLEncodingMixin
+    */
+  class GrChangeListItem extends Polymer.mixinBehaviors( [
+    Gerrit.BaseUrlBehavior,
+    Gerrit.ChangeTableBehavior,
+    Gerrit.PathListBehavior,
+    Gerrit.RESTClientBehavior,
+    Gerrit.URLEncodingBehavior,
+  ], Polymer.GestureEventListeners(
+      Polymer.LegacyElementMixin(
+          Polymer.Element))) {
+    static get is() { return 'gr-change-list-item'; }
 
-    properties: {
-      visibleChangeTableColumns: Array,
-      labelNames: {
-        type: Array,
-      },
+    static get properties() {
+      return {
+        visibleChangeTableColumns: Array,
+        labelNames: {
+          type: Array,
+        },
 
-      /** @type {?} */
-      change: Object,
-      changeURL: {
-        type: String,
-        computed: '_computeChangeURL(change)',
-      },
-      statuses: {
-        type: Array,
-        computed: 'changeStatuses(change)',
-      },
-      showStar: {
-        type: Boolean,
-        value: false,
-      },
-      showNumber: Boolean,
-      _changeSize: {
-        type: String,
-        computed: '_computeChangeSize(change)',
-      },
-      _dynamicCellEndpoints: {
-        type: Array,
-      },
-    },
-
-    behaviors: [
-      Gerrit.BaseUrlBehavior,
-      Gerrit.ChangeTableBehavior,
-      Gerrit.PathListBehavior,
-      Gerrit.RESTClientBehavior,
-      Gerrit.URLEncodingBehavior,
-    ],
+        /** @type {?} */
+        change: Object,
+        changeURL: {
+          type: String,
+          computed: '_computeChangeURL(change)',
+        },
+        statuses: {
+          type: Array,
+          computed: 'changeStatuses(change)',
+        },
+        showStar: {
+          type: Boolean,
+          value: false,
+        },
+        showNumber: Boolean,
+        _changeSize: {
+          type: String,
+          computed: '_computeChangeSize(change)',
+        },
+        _dynamicCellEndpoints: {
+          type: Array,
+        },
+      };
+    }
 
     attached() {
+      super.attached();
       Gerrit.awaitPluginsLoaded().then(() => {
         this._dynamicCellEndpoints = Gerrit._endpoints.getDynamicEndpoints(
             'change-list-item-cell');
       });
-    },
+    }
 
     _computeChangeURL(change) {
       return Gerrit.Nav.getUrlForChange(change);
-    },
+    }
 
     _computeLabelTitle(change, labelName) {
       const label = change.labels[labelName];
@@ -85,7 +95,7 @@
         return labelName + '\nby ' + significantLabel.name;
       }
       return labelName;
-    },
+    }
 
     _computeLabelClass(change, labelName) {
       const label = change.labels[labelName];
@@ -112,7 +122,7 @@
         classes['u-gray-background'] = true;
       }
       return Object.keys(classes).sort().join(' ');
-    },
+    }
 
     _computeLabelValue(change, labelName) {
       const label = change.labels[labelName];
@@ -130,22 +140,22 @@
         return label.value;
       }
       return '';
-    },
+    }
 
     _computeRepoUrl(change) {
       return Gerrit.Nav.getUrlForProjectChanges(change.project, true,
           change.internalHost);
-    },
+    }
 
     _computeRepoBranchURL(change) {
       return Gerrit.Nav.getUrlForBranch(change.branch, change.project, null,
           change.internalHost);
-    },
+    }
 
     _computeTopicURL(change) {
       if (!change.topic) { return ''; }
       return Gerrit.Nav.getUrlForTopic(change.topic, change.internalHost);
-    },
+    }
 
     /**
      * Computes the display string for the project column. If there is a host
@@ -162,7 +172,7 @@
       if (change.internalHost) { str += change.internalHost + '/'; }
       str += truncate ? this.truncatePath(change.project, 2) : change.project;
       return str;
-    },
+    }
 
     _computeSizeTooltip(change) {
       if (change.insertions + change.deletions === 0 ||
@@ -171,7 +181,7 @@
       } else {
         return `+${change.insertions}, -${change.deletions}`;
       }
-    },
+    }
 
     /**
      * TShirt sizing is based on the following paper:
@@ -193,7 +203,7 @@
       } else {
         return 'XL';
       }
-    },
+    }
 
     toggleReviewed() {
       const newVal = !this.change.reviewed;
@@ -203,6 +213,8 @@
         composed: true,
         detail: {change: this.change, reviewed: newVal},
       }));
-    },
-  });
+    }
+  }
+
+  customElements.define(GrChangeListItem.is, GrChangeListItem);
 })();
diff --git a/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item_test.html b/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item_test.html
index 12b4202..3c402d1 100644
--- a/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item_test.html
+++ b/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-change-list-item</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>
diff --git a/polygerrit-ui/app/elements/change-list/gr-change-list-view/gr-change-list-view.js b/polygerrit-ui/app/elements/change-list/gr-change-list-view/gr-change-list-view.js
index 02e9a04..49aa1eb 100644
--- a/polygerrit-ui/app/elements/change-list/gr-change-list-view/gr-change-list-view.js
+++ b/polygerrit-ui/app/elements/change-list/gr-change-list-view/gr-change-list-view.js
@@ -29,44 +29,49 @@
 
   const LIMIT_OPERATOR_PATTERN = /\blimit:(\d+)/i;
 
-  Polymer({
-    is: 'gr-change-list-view',
-
+  /**
+    * @appliesMixin Gerrit.BaseUrlMixin
+    * @appliesMixin Gerrit.FireMixin
+    * @appliesMixin Gerrit.URLEncodingMixin
+    */
+  class GrChangeListView extends Polymer.mixinBehaviors( [
+    Gerrit.BaseUrlBehavior,
+    Gerrit.FireBehavior,
+    Gerrit.URLEncodingBehavior,
+  ], Polymer.GestureEventListeners(
+      Polymer.LegacyElementMixin(
+          Polymer.Element))) {
+    static get is() { return 'gr-change-list-view'; }
     /**
      * Fired when the title of the page should change.
      *
      * @event title-change
      */
 
-    behaviors: [
-      Gerrit.BaseUrlBehavior,
-      Gerrit.FireBehavior,
-      Gerrit.URLEncodingBehavior,
-    ],
-
-    properties: {
+    static get properties() {
+      return {
       /**
        * URL params passed from the router.
        */
-      params: {
-        type: Object,
-        observer: '_paramsChanged',
-      },
+        params: {
+          type: Object,
+          observer: '_paramsChanged',
+        },
 
-      /**
+        /**
        * True when user is logged in.
        */
-      _loggedIn: {
-        type: Boolean,
-        computed: '_computeLoggedIn(account)',
-      },
+        _loggedIn: {
+          type: Boolean,
+          computed: '_computeLoggedIn(account)',
+        },
 
-      account: {
-        type: Object,
-        value: null,
-      },
+        account: {
+          type: Object,
+          value: null,
+        },
 
-      /**
+        /**
        * State persisted across restamps of the element.
        *
        * Need sub-property declaration since it is used in template before
@@ -74,66 +79,71 @@
        * @type {{ selectedChangeIndex: (number|undefined) }}
        *
        */
-      viewState: {
-        type: Object,
-        notify: true,
-        value() { return {}; },
-      },
+        viewState: {
+          type: Object,
+          notify: true,
+          value() { return {}; },
+        },
 
-      preferences: Object,
+        preferences: Object,
 
-      _changesPerPage: Number,
+        _changesPerPage: Number,
 
-      /**
+        /**
        * Currently active query.
        */
-      _query: {
-        type: String,
-        value: '',
-      },
+        _query: {
+          type: String,
+          value: '',
+        },
 
-      /**
+        /**
        * Offset of currently visible query results.
        */
-      _offset: Number,
+        _offset: Number,
 
-      /**
+        /**
        * Change objects loaded from the server.
        */
-      _changes: {
-        type: Array,
-        observer: '_changesChanged',
-      },
+        _changes: {
+          type: Array,
+          observer: '_changesChanged',
+        },
 
-      /**
+        /**
        * For showing a "loading..." string during ajax requests.
        */
-      _loading: {
-        type: Boolean,
-        value: true,
-      },
+        _loading: {
+          type: Boolean,
+          value: true,
+        },
 
-      /** @type {?String} */
-      _userId: {
-        type: String,
-        value: null,
-      },
+        /** @type {?String} */
+        _userId: {
+          type: String,
+          value: null,
+        },
 
-      /** @type {?String} */
-      _repo: {
-        type: String,
-        value: null,
-      },
-    },
+        /** @type {?String} */
+        _repo: {
+          type: String,
+          value: null,
+        },
+      };
+    }
 
-    listeners: {
-      'next-page': '_handleNextPage',
-      'previous-page': '_handlePreviousPage',
-    },
+    created() {
+      super.created();
+      this.addEventListener('next-page',
+          () => this._handleNextPage());
+      this.addEventListener('previous-page',
+          () => this._handlePreviousPage());
+    }
 
     attached() {
+      super.attached();
       this._loadPreferences();
-    },
+    }
 
     _paramsChanged(value) {
       if (value.view !== Gerrit.Nav.View.SEARCH) { return; }
@@ -170,7 +180,7 @@
         this._changes = changes;
         this._loading = false;
       });
-    },
+    }
 
     _loadPreferences() {
       return this.$.restAPI.getLoggedIn().then(loggedIn => {
@@ -182,20 +192,20 @@
           this.preferences = {};
         }
       });
-    },
+    }
 
     _replaceCurrentLocation(url) {
       window.location.replace(url);
-    },
+    }
 
     _getChanges() {
       return this.$.restAPI.getChanges(this._changesPerPage, this._query,
           this._offset);
-    },
+    }
 
     _getPreferences() {
       return this.$.restAPI.getPreferences();
-    },
+    }
 
     _limitFor(query, defaultLimit) {
       const match = query.match(LIMIT_OPERATOR_PATTERN);
@@ -203,7 +213,7 @@
         return defaultLimit;
       }
       return parseInt(match[1], 10);
-    },
+    }
 
     _computeNavLink(query, offset, direction, changesPerPage) {
       // Offset could be a string when passed from the router.
@@ -211,32 +221,32 @@
       const limit = this._limitFor(query, changesPerPage);
       const newOffset = Math.max(0, offset + (limit * direction));
       return Gerrit.Nav.getUrlForSearchQuery(query, newOffset);
-    },
+    }
 
     _computePrevArrowClass(offset) {
       return offset === 0 ? 'hide' : '';
-    },
+    }
 
     _computeNextArrowClass(changes) {
       const more = changes.length && changes[changes.length - 1]._more_changes;
       return more ? '' : 'hide';
-    },
+    }
 
     _computeNavClass(loading) {
       return loading || !this._changes || !this._changes.length ? 'hide' : '';
-    },
+    }
 
     _handleNextPage() {
       if (this.$.nextArrow.hidden) { return; }
       page.show(this._computeNavLink(
           this._query, this._offset, 1, this._changesPerPage));
-    },
+    }
 
     _handlePreviousPage() {
       if (this.$.prevArrow.hidden) { return; }
       page.show(this._computeNavLink(
           this._query, this._offset, -1, this._changesPerPage));
-    },
+    }
 
     _changesChanged(changes) {
       this._userId = null;
@@ -255,28 +265,30 @@
       if (REPO_QUERY_PATTERN.test(this._query)) {
         this._repo = changes[0].project;
       }
-    },
+    }
 
     _computeHeaderClass(id) {
       return id ? '' : 'hide';
-    },
+    }
 
     _computePage(offset, changesPerPage) {
       return offset / changesPerPage + 1;
-    },
+    }
 
     _computeLoggedIn(account) {
       return !!(account && Object.keys(account).length > 0);
-    },
+    }
 
     _handleToggleStar(e) {
       this.$.restAPI.saveChangeStarred(e.detail.change._number,
           e.detail.starred);
-    },
+    }
 
     _handleToggleReviewed(e) {
       this.$.restAPI.saveChangeReviewed(e.detail.change._number,
           e.detail.reviewed);
-    },
-  });
+    }
+  }
+
+  customElements.define(GrChangeListView.is, GrChangeListView);
 })();
diff --git a/polygerrit-ui/app/elements/change-list/gr-change-list-view/gr-change-list-view_test.html b/polygerrit-ui/app/elements/change-list/gr-change-list-view/gr-change-list-view_test.html
index 2367aac..08a83af 100644
--- a/polygerrit-ui/app/elements/change-list/gr-change-list-view/gr-change-list-view_test.html
+++ b/polygerrit-ui/app/elements/change-list/gr-change-list-view/gr-change-list-view_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-change-list-view</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/page/page.js"></script>
diff --git a/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list.js b/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list.js
index 5006f1e..5ac4fcd 100644
--- a/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list.js
+++ b/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list.js
@@ -22,9 +22,25 @@
   const LABEL_PREFIX_INVALID_PROLOG = 'Invalid-Prolog-Rules-Label-Name--';
   const MAX_SHORTCUT_CHARS = 5;
 
-  Polymer({
-    is: 'gr-change-list',
-
+  /**
+    * @appliesMixin Gerrit.BaseUrlMixin
+    * @appliesMixin Gerrit.ChangeTableMixin
+    * @appliesMixin Gerrit.FireMixin
+    * @appliesMixin Gerrit.KeyboardShortcutMixin
+    * @appliesMixin Gerrit.RESTClientMixin
+    * @appliesMixin Gerrit.URLEncodingMixin
+    */
+  class GrChangeList extends Polymer.mixinBehaviors( [
+    Gerrit.BaseUrlBehavior,
+    Gerrit.ChangeTableBehavior,
+    Gerrit.FireBehavior,
+    Gerrit.KeyboardShortcutBehavior,
+    Gerrit.RESTClientBehavior,
+    Gerrit.URLEncodingBehavior,
+  ], Polymer.GestureEventListeners(
+      Polymer.LegacyElementMixin(
+          Polymer.Element))) {
+    static get is() { return 'gr-change-list'; }
     /**
      * Fired when next page key shortcut was pressed.
      *
@@ -37,28 +53,25 @@
      * @event previous-page
      */
 
-    hostAttributes: {
-      tabindex: 0,
-    },
-
-    properties: {
+    static get properties() {
+      return {
       /**
        * The logged-in user's account, or an empty object if no user is logged
        * in.
        */
-      account: {
-        type: Object,
-        value: null,
-      },
-      /**
+        account: {
+          type: Object,
+          value: null,
+        },
+        /**
        * An array of ChangeInfo objects to render.
        * https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#change-info
        */
-      changes: {
-        type: Array,
-        observer: '_changesChanged',
-      },
-      /**
+        changes: {
+          type: Array,
+          observer: '_changesChanged',
+        },
+        /**
        * ChangeInfo objects grouped into arrays. The sections and changes
        * properties should not be used together.
        *
@@ -68,56 +81,46 @@
        *   results: !Array<!Object>
        * }>}
        */
-      sections: {
-        type: Array,
-        value() { return []; },
-      },
-      labelNames: {
-        type: Array,
-        computed: '_computeLabelNames(sections)',
-      },
-      _dynamicHeaderEndpoints: {
-        type: Array,
-      },
-      selectedIndex: {
-        type: Number,
-        notify: true,
-      },
-      showNumber: Boolean, // No default value to prevent flickering.
-      showStar: {
-        type: Boolean,
-        value: false,
-      },
-      showReviewedState: {
-        type: Boolean,
-        value: false,
-      },
-      keyEventTarget: {
-        type: Object,
-        value() { return document.body; },
-      },
-      changeTableColumns: Array,
-      visibleChangeTableColumns: Array,
-      preferences: Object,
-    },
+        sections: {
+          type: Array,
+          value() { return []; },
+        },
+        labelNames: {
+          type: Array,
+          computed: '_computeLabelNames(sections)',
+        },
+        _dynamicHeaderEndpoints: {
+          type: Array,
+        },
+        selectedIndex: {
+          type: Number,
+          notify: true,
+        },
+        showNumber: Boolean, // No default value to prevent flickering.
+        showStar: {
+          type: Boolean,
+          value: false,
+        },
+        showReviewedState: {
+          type: Boolean,
+          value: false,
+        },
+        keyEventTarget: {
+          type: Object,
+          value() { return document.body; },
+        },
+        changeTableColumns: Array,
+        visibleChangeTableColumns: Array,
+        preferences: Object,
+      };
+    }
 
-    behaviors: [
-      Gerrit.BaseUrlBehavior,
-      Gerrit.ChangeTableBehavior,
-      Gerrit.FireBehavior,
-      Gerrit.KeyboardShortcutBehavior,
-      Gerrit.RESTClientBehavior,
-      Gerrit.URLEncodingBehavior,
-    ],
-
-    listeners: {
-      keydown: '_scopedKeydownHandler',
-    },
-
-    observers: [
-      '_sectionsChanged(sections.*)',
-      '_computePreferences(account, preferences)',
-    ],
+    static get observers() {
+      return [
+        '_sectionsChanged(sections.*)',
+        '_computePreferences(account, preferences)',
+      ];
+    }
 
     keyboardShortcuts() {
       return {
@@ -130,14 +133,26 @@
         [this.Shortcut.TOGGLE_CHANGE_STAR]: '_toggleChangeStar',
         [this.Shortcut.REFRESH_CHANGE_LIST]: '_refreshChangeList',
       };
-    },
+    }
+
+    created() {
+      super.created();
+      this.addEventListener('keydown',
+          e => this._scopedKeydownHandler(e));
+    }
+
+    ready() {
+      super.ready();
+      this._ensureAttribute('tabindex', 0);
+    }
 
     attached() {
+      super.attached();
       Gerrit.awaitPluginsLoaded().then(() => {
         this._dynamicHeaderEndpoints = Gerrit._endpoints.getDynamicEndpoints(
             'change-list-header');
       });
-    },
+    }
 
     /**
      * Iron-a11y-keys-behavior catches keyboard events globally. Some keyboard
@@ -151,11 +166,11 @@
         // Enter.
         this._openChange(e);
       }
-    },
+    }
 
     _lowerCase(column) {
       return column.toLowerCase();
-    },
+    }
 
     _computePreferences(account, preferences) {
       // Polymer 2: check for undefined
@@ -175,13 +190,13 @@
         this.showNumber = false;
         this.visibleChangeTableColumns = this.columnNames;
       }
-    },
+    }
 
     _computeColspan(changeTableColumns, labelNames) {
       if (!changeTableColumns || !labelNames) return;
       return changeTableColumns.length + labelNames.length +
           NUMBER_FIXED_COLUMNS;
-    },
+    }
 
     _computeLabelNames(sections) {
       if (!sections) { return []; }
@@ -198,7 +213,7 @@
         }
       }
       return labels.sort();
-    },
+    }
 
     _computeLabelShortcut(labelName) {
       if (labelName.startsWith(LABEL_PREFIX_INVALID_PROLOG)) {
@@ -210,11 +225,11 @@
             return a + i[0].toUpperCase();
           }, '')
           .slice(0, MAX_SHORTCUT_CHARS);
-    },
+    }
 
     _changesChanged(changes) {
       this.sections = changes ? [{results: changes}] : [];
-    },
+    }
 
     _processQuery(query) {
       let tokens = query.split(' ');
@@ -225,11 +240,11 @@
         });
       });
       return tokens.join(' ');
-    },
+    }
 
     _sectionHref(query) {
       return Gerrit.Nav.getUrlForSearchQuery(this._processQuery(query));
-    },
+    }
 
     /**
      * Maps an index local to a particular section to the absolute index
@@ -245,19 +260,19 @@
         idx += this.sections[i].results.length;
       }
       return idx + localIndex;
-    },
+    }
 
     _computeItemSelected(sectionIndex, index, selectedIndex) {
       const idx = this._computeItemAbsoluteIndex(sectionIndex, index);
       return idx == selectedIndex;
-    },
+    }
 
     _computeItemNeedsReview(account, change, showReviewedState) {
       return showReviewedState && !change.reviewed &&
           !change.work_in_progress &&
           this.changeIsOpen(change) &&
           (!account || account._account_id != change.owner._account_id);
-    },
+    }
 
     _computeItemHighlight(account, change) {
       // Do not show the assignee highlight if the change is not open.
@@ -267,7 +282,7 @@
         return false;
       }
       return account._account_id === change.assignee._account_id;
-    },
+    }
 
     _nextChange(e) {
       if (this.shouldSuppressKeyboardShortcut(e) ||
@@ -275,7 +290,7 @@
 
       e.preventDefault();
       this.$.cursor.next();
-    },
+    }
 
     _prevChange(e) {
       if (this.shouldSuppressKeyboardShortcut(e) ||
@@ -283,7 +298,7 @@
 
       e.preventDefault();
       this.$.cursor.previous();
-    },
+    }
 
     _openChange(e) {
       if (this.shouldSuppressKeyboardShortcut(e) ||
@@ -291,7 +306,7 @@
 
       e.preventDefault();
       Gerrit.Nav.navigateToChange(this._changeForIndex(this.selectedIndex));
-    },
+    }
 
     _nextPage(e) {
       if (this.shouldSuppressKeyboardShortcut(e) ||
@@ -301,7 +316,7 @@
 
       e.preventDefault();
       this.fire('next-page');
-    },
+    }
 
     _prevPage(e) {
       if (this.shouldSuppressKeyboardShortcut(e) ||
@@ -311,7 +326,7 @@
 
       e.preventDefault();
       this.fire('previous-page');
-    },
+    }
 
     _toggleChangeReviewed(e) {
       if (this.shouldSuppressKeyboardShortcut(e) ||
@@ -319,7 +334,7 @@
 
       e.preventDefault();
       this._toggleReviewedForIndex(this.selectedIndex);
-    },
+    }
 
     _toggleReviewedForIndex(index) {
       const changeEls = this._getListItems();
@@ -329,18 +344,18 @@
 
       const changeEl = changeEls[index];
       changeEl.toggleReviewed();
-    },
+    }
 
     _refreshChangeList(e) {
       if (this.shouldSuppressKeyboardShortcut(e)) { return; }
 
       e.preventDefault();
       this._reloadWindow();
-    },
+    }
 
     _reloadWindow() {
       window.location.reload();
-    },
+    }
 
     _toggleChangeStar(e) {
       if (this.shouldSuppressKeyboardShortcut(e) ||
@@ -348,7 +363,7 @@
 
       e.preventDefault();
       this._toggleStarForIndex(this.selectedIndex);
-    },
+    }
 
     _toggleStarForIndex(index) {
       const changeEls = this._getListItems();
@@ -358,7 +373,7 @@
 
       const changeEl = changeEls[index];
       changeEl.$$('gr-change-star').toggleStar();
-    },
+    }
 
     _changeForIndex(index) {
       const changeEls = this._getListItems();
@@ -366,12 +381,12 @@
         return changeEls[index].change;
       }
       return null;
-    },
+    }
 
     _getListItems() {
       return Array.from(
           Polymer.dom(this.root).querySelectorAll('gr-change-list-item'));
-    },
+    }
 
     _sectionsChanged() {
       // Flush DOM operations so that the list item elements will be loaded.
@@ -379,14 +394,16 @@
         this.$.cursor.stops = this._getListItems();
         this.$.cursor.moveToStart();
       });
-    },
+    }
 
     _isOutgoing(section) {
       return !!section.isOutgoing;
-    },
+    }
 
     _isEmpty(section) {
       return !section.results.length;
-    },
-  });
+    }
+  }
+
+  customElements.define(GrChangeList.is, GrChangeList);
 })();
diff --git a/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list_test.html b/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list_test.html
index 817de6f..75fd167 100644
--- a/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list_test.html
+++ b/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-change-list</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>
diff --git a/polygerrit-ui/app/elements/change-list/gr-create-change-help/gr-create-change-help.js b/polygerrit-ui/app/elements/change-list/gr-create-change-help/gr-create-change-help.js
index 19e7a25..e6caacb 100644
--- a/polygerrit-ui/app/elements/change-list/gr-create-change-help/gr-create-change-help.js
+++ b/polygerrit-ui/app/elements/change-list/gr-create-change-help/gr-create-change-help.js
@@ -17,8 +17,10 @@
 (function() {
   'use strict';
 
-  Polymer({
-    is: 'gr-create-change-help',
+  class GrCreateChangeHelp extends Polymer.GestureEventListeners(
+      Polymer.LegacyElementMixin(
+          Polymer.Element)) {
+    static get is() { return 'gr-create-change-help'; }
 
     /**
      * Fired when the "Create change" button is tapped.
@@ -30,6 +32,8 @@
       e.preventDefault();
       this.dispatchEvent(
           new CustomEvent('create-tap', {bubbles: true, composed: true}));
-    },
-  });
+    }
+  }
+
+  customElements.define(GrCreateChangeHelp.is, GrCreateChangeHelp);
 })();
diff --git a/polygerrit-ui/app/elements/change-list/gr-create-change-help/gr-create-change-help_test.html b/polygerrit-ui/app/elements/change-list/gr-create-change-help/gr-create-change-help_test.html
index c43d62a..f10fbf2 100644
--- a/polygerrit-ui/app/elements/change-list/gr-create-change-help/gr-create-change-help_test.html
+++ b/polygerrit-ui/app/elements/change-list/gr-create-change-help/gr-create-change-help_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-create-change-help</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>
diff --git a/polygerrit-ui/app/elements/change-list/gr-create-commands-dialog/gr-create-commands-dialog.js b/polygerrit-ui/app/elements/change-list/gr-create-commands-dialog/gr-create-commands-dialog.js
index 5abb257..303128b 100644
--- a/polygerrit-ui/app/elements/change-list/gr-create-commands-dialog/gr-create-commands-dialog.js
+++ b/polygerrit-ui/app/elements/change-list/gr-create-commands-dialog/gr-create-commands-dialog.js
@@ -23,37 +23,43 @@
     PUSH_PREFIX: 'git push origin HEAD:refs/for/',
   };
 
-  Polymer({
-    is: 'gr-create-commands-dialog',
+  class GrCreateCommandsDialog extends Polymer.GestureEventListeners(
+      Polymer.LegacyElementMixin(
+          Polymer.Element)) {
+    static get is() { return 'gr-create-commands-dialog'; }
 
-    properties: {
-      branch: String,
-      _createNewCommitCommand: {
-        type: String,
-        readonly: true,
-        value: Commands.CREATE,
-      },
-      _amendExistingCommitCommand: {
-        type: String,
-        readonly: true,
-        value: Commands.AMEND,
-      },
-      _pushCommand: {
-        type: String,
-        computed: '_computePushCommand(branch)',
-      },
-    },
+    static get properties() {
+      return {
+        branch: String,
+        _createNewCommitCommand: {
+          type: String,
+          readonly: true,
+          value: Commands.CREATE,
+        },
+        _amendExistingCommitCommand: {
+          type: String,
+          readonly: true,
+          value: Commands.AMEND,
+        },
+        _pushCommand: {
+          type: String,
+          computed: '_computePushCommand(branch)',
+        },
+      };
+    }
 
     open() {
       this.$.commandsOverlay.open();
-    },
+    }
 
     _handleClose() {
       this.$.commandsOverlay.close();
-    },
+    }
 
     _computePushCommand(branch) {
       return Commands.PUSH_PREFIX + branch;
-    },
-  });
+    }
+  }
+
+  customElements.define(GrCreateCommandsDialog.is, GrCreateCommandsDialog);
 })();
diff --git a/polygerrit-ui/app/elements/change-list/gr-create-commands-dialog/gr-create-commands-dialog_test.html b/polygerrit-ui/app/elements/change-list/gr-create-commands-dialog/gr-create-commands-dialog_test.html
index 89ad573..2228b50 100644
--- a/polygerrit-ui/app/elements/change-list/gr-create-commands-dialog/gr-create-commands-dialog_test.html
+++ b/polygerrit-ui/app/elements/change-list/gr-create-commands-dialog/gr-create-commands-dialog_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-create-commands-dialog</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>
diff --git a/polygerrit-ui/app/elements/change-list/gr-create-destination-dialog/gr-create-destination-dialog.js b/polygerrit-ui/app/elements/change-list/gr-create-destination-dialog/gr-create-destination-dialog.js
index d601cad..3652e70 100644
--- a/polygerrit-ui/app/elements/change-list/gr-create-destination-dialog/gr-create-destination-dialog.js
+++ b/polygerrit-ui/app/elements/change-list/gr-create-destination-dialog/gr-create-destination-dialog.js
@@ -23,37 +23,44 @@
    *
    * @event confirm
    */
+  class GrCreateDestinationDialog extends Polymer.GestureEventListeners(
+      Polymer.LegacyElementMixin(
+          Polymer.Element)) {
+    static get is() { return 'gr-create-destination-dialog'; }
 
-  Polymer({
-    is: 'gr-create-destination-dialog',
+    static get properties() {
+      return {
+        _repo: String,
+        _branch: String,
+        _repoAndBranchSelected: {
+          type: Boolean,
+          value: false,
+          computed: '_computeRepoAndBranchSelected(_repo, _branch)',
+        },
+      };
+    }
 
-    properties: {
-      _repo: String,
-      _branch: String,
-      _repoAndBranchSelected: {
-        type: Boolean,
-        value: false,
-        computed: '_computeRepoAndBranchSelected(_repo, _branch)',
-      },
-    },
     open() {
       this._repo = '';
       this._branch = '';
       this.$.createOverlay.open();
-    },
+    }
 
     _handleClose() {
       this.$.createOverlay.close();
-    },
+    }
 
     _pickerConfirm() {
       this.$.createOverlay.close();
       const detail = {repo: this._repo, branch: this._branch};
       this.dispatchEvent(new CustomEvent('confirm', {detail, bubbles: false}));
-    },
+    }
 
     _computeRepoAndBranchSelected(repo, branch) {
       return !!(repo && branch);
-    },
-  });
+    }
+  }
+
+  customElements.define(GrCreateDestinationDialog.is,
+      GrCreateDestinationDialog);
 })();
diff --git a/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view.js b/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view.js
index b762ab3..69cc070 100644
--- a/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view.js
+++ b/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view.js
@@ -19,65 +19,72 @@
 
   const PROJECT_PLACEHOLDER_PATTERN = /\$\{project\}/g;
 
-  Polymer({
-    is: 'gr-dashboard-view',
-
+  /**
+    * @appliesMixin Gerrit.FireMixin
+    * @appliesMixin Gerrit.RESTClientMixin
+    */
+  class GrDashboardView extends Polymer.mixinBehaviors( [
+    Gerrit.FireBehavior,
+    Gerrit.RESTClientBehavior,
+  ], Polymer.GestureEventListeners(
+      Polymer.LegacyElementMixin(
+          Polymer.Element))) {
+    static get is() { return 'gr-dashboard-view'; }
     /**
      * Fired when the title of the page should change.
      *
      * @event title-change
      */
 
-    properties: {
-      account: {
-        type: Object,
-        value: null,
-      },
-      preferences: Object,
-      /** @type {{ selectedChangeIndex: number }} */
-      viewState: Object,
-
-      /** @type {{ project: string, user: string }} */
-      params: {
-        type: Object,
-      },
-
-      createChangeTap: {
-        type: Function,
-        value() {
-          return this._createChangeTap.bind(this);
+    static get properties() {
+      return {
+        account: {
+          type: Object,
+          value: null,
         },
-      },
+        preferences: Object,
+        /** @type {{ selectedChangeIndex: number }} */
+        viewState: Object,
 
-      _results: Array,
+        /** @type {{ project: string, user: string }} */
+        params: {
+          type: Object,
+        },
 
-      /**
+        createChangeTap: {
+          type: Function,
+          value() {
+            return this._createChangeTap.bind(this);
+          },
+        },
+
+        _results: Array,
+
+        /**
        * For showing a "loading..." string during ajax requests.
        */
-      _loading: {
-        type: Boolean,
-        value: true,
-      },
+        _loading: {
+          type: Boolean,
+          value: true,
+        },
 
-      _showDraftsBanner: {
-        type: Boolean,
-        value: false,
-      },
+        _showDraftsBanner: {
+          type: Boolean,
+          value: false,
+        },
 
-      _showNewUserHelp: {
-        type: Boolean,
-        value: false,
-      },
-    },
+        _showNewUserHelp: {
+          type: Boolean,
+          value: false,
+        },
+      };
+    }
 
-    observers: [
-      '_paramsChanged(params.*)',
-    ],
-
-    behaviors: [
-      Gerrit.FireBehavior,
-      Gerrit.RESTClientBehavior,
-    ],
+    static get observers() {
+      return [
+        '_paramsChanged(params.*)',
+      ];
+    }
 
     get options() {
       return this.listChangesOptionsToHex(
@@ -85,11 +92,12 @@
           this.ListChangesOption.DETAILED_ACCOUNTS,
           this.ListChangesOption.REVIEWED
       );
-    },
+    }
 
     attached() {
+      super.attached();
       this._loadPreferences();
-    },
+    }
 
     _loadPreferences() {
       return this.$.restAPI.getLoggedIn().then(loggedIn => {
@@ -101,7 +109,7 @@
           this.preferences = {};
         }
       });
-    },
+    }
 
     _getProjectDashboard(project, dashboard) {
       const errFn = response => {
@@ -124,18 +132,18 @@
           }),
         };
       });
-    },
+    }
 
     _computeTitle(user) {
       if (!user || user === 'self') {
         return 'My Reviews';
       }
       return 'Dashboard for ' + user;
-    },
+    }
 
     _isViewActive(params) {
       return params.view === Gerrit.Nav.View.DASHBOARD;
-    },
+    }
 
     _paramsChanged(paramsChangeRecord) {
       const params = paramsChangeRecord.base;
@@ -145,7 +153,7 @@
       }
 
       return this._reload();
-    },
+    }
 
     /**
      * Reloads the element.
@@ -179,7 +187,7 @@
             });
             console.warn(err);
           }).then(() => { this._loading = false; });
-    },
+    }
 
     /**
      * Fetches the changes for each dashboard section and sets this._results
@@ -218,7 +226,7 @@
               !res.sections[i].hideIfEmpty ||
                 section.results.length));
           });
-    },
+    }
 
     _computeSectionCountLabel(changes) {
       if (!changes || !changes.length || changes.length == 0) {
@@ -228,7 +236,7 @@
       const numChanges = changes.length;
       const andMore = more ? ' and more' : '';
       return `(${numChanges}${andMore})`;
-    },
+    }
 
     _computeUserHeaderClass(params) {
       if (!params || !!params.project || !params.user
@@ -236,17 +244,17 @@
         return 'hide';
       }
       return '';
-    },
+    }
 
     _handleToggleStar(e) {
       this.$.restAPI.saveChangeStarred(e.detail.change._number,
           e.detail.starred);
-    },
+    }
 
     _handleToggleReviewed(e) {
       this.$.restAPI.saveChangeReviewed(e.detail.change._number,
           e.detail.reviewed);
-    },
+    }
 
     /**
      * Banner is shown if a user is on their own dashboard and they have draft
@@ -265,15 +273,15 @@
       if (!closedChanges.length) { return; }
 
       this._showDraftsBanner = true;
-    },
+    }
 
     _computeBannerClass(show) {
       return show ? '' : 'hide';
-    },
+    }
 
     _handleOpenDeleteDialog() {
       this.$.confirmDeleteOverlay.open();
-    },
+    }
 
     _handleConfirmDelete() {
       this.$.confirmDeleteDialog.disabled = true;
@@ -281,23 +289,25 @@
         this._closeConfirmDeleteOverlay();
         this._reload();
       });
-    },
+    }
 
     _closeConfirmDeleteOverlay() {
       this.$.confirmDeleteOverlay.close();
-    },
+    }
 
     _computeDraftsLink() {
       return Gerrit.Nav.getUrlForSearchQuery('has:draft -is:open');
-    },
+    }
 
     _createChangeTap(e) {
       this.$.destinationDialog.open();
-    },
+    }
 
     _handleDestinationConfirm(e) {
       this.$.commandsDialog.branch = e.detail.branch;
       this.$.commandsDialog.open();
-    },
-  });
+    }
+  }
+
+  customElements.define(GrDashboardView.is, GrDashboardView);
 })();
diff --git a/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view_test.html b/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view_test.html
index 41d4192..0d15d13 100644
--- a/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view_test.html
+++ b/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-dashboard-view</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>
diff --git a/polygerrit-ui/app/elements/change-list/gr-embed-dashboard/gr-embed-dashboard.js b/polygerrit-ui/app/elements/change-list/gr-embed-dashboard/gr-embed-dashboard.js
index acc4295..96dcb98 100644
--- a/polygerrit-ui/app/elements/change-list/gr-embed-dashboard/gr-embed-dashboard.js
+++ b/polygerrit-ui/app/elements/change-list/gr-embed-dashboard/gr-embed-dashboard.js
@@ -17,14 +17,20 @@
 (function() {
   'use strict';
 
-  Polymer({
-    is: 'gr-embed-dashboard',
+  class GrEmbedDashboard extends Polymer.GestureEventListeners(
+      Polymer.LegacyElementMixin(
+          Polymer.Element)) {
+    static get is() { return 'gr-embed-dashboard'; }
 
-    properties: {
-      account: Object,
-      sections: Array,
-      preferences: Object,
-      showNewUserHelp: Boolean,
-    },
-  });
+    static get properties() {
+      return {
+        account: Object,
+        sections: Array,
+        preferences: Object,
+        showNewUserHelp: Boolean,
+      };
+    }
+  }
+
+  customElements.define(GrEmbedDashboard.is, GrEmbedDashboard);
 })();
diff --git a/polygerrit-ui/app/elements/change-list/gr-repo-header/gr-repo-header.js b/polygerrit-ui/app/elements/change-list/gr-repo-header/gr-repo-header.js
index 7ae4dab..c603c45 100644
--- a/polygerrit-ui/app/elements/change-list/gr-repo-header/gr-repo-header.js
+++ b/polygerrit-ui/app/elements/change-list/gr-repo-header/gr-repo-header.js
@@ -17,18 +17,22 @@
 (function() {
   'use strict';
 
-  Polymer({
-    is: 'gr-repo-header',
+  class GrRepoHeader extends Polymer.GestureEventListeners(
+      Polymer.LegacyElementMixin(
+          Polymer.Element)) {
+    static get is() { return 'gr-repo-header'; }
 
-    properties: {
+    static get properties() {
+      return {
       /** @type {?String} */
-      repo: {
-        type: String,
-        observer: '_repoChanged',
-      },
-      /** @type {String|null} */
-      _repoUrl: String,
-    },
+        repo: {
+          type: String,
+          observer: '_repoChanged',
+        },
+        /** @type {String|null} */
+        _repoUrl: String,
+      };
+    }
 
     _repoChanged(repoName) {
       if (!repoName) {
@@ -36,6 +40,8 @@
         return;
       }
       this._repoUrl = Gerrit.Nav.getUrlForRepo(repoName);
-    },
-  });
+    }
+  }
+
+  customElements.define(GrRepoHeader.is, GrRepoHeader);
 })();
diff --git a/polygerrit-ui/app/elements/change-list/gr-repo-header/gr-repo-header_test.html b/polygerrit-ui/app/elements/change-list/gr-repo-header/gr-repo-header_test.html
index 266818e..85eb509 100644
--- a/polygerrit-ui/app/elements/change-list/gr-repo-header/gr-repo-header_test.html
+++ b/polygerrit-ui/app/elements/change-list/gr-repo-header/gr-repo-header_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-repo-header</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>
diff --git a/polygerrit-ui/app/elements/change-list/gr-user-header/gr-user-header.js b/polygerrit-ui/app/elements/change-list/gr-user-header/gr-user-header.js
index 6afc169..af8af3f 100644
--- a/polygerrit-ui/app/elements/change-list/gr-user-header/gr-user-header.js
+++ b/polygerrit-ui/app/elements/change-list/gr-user-header/gr-user-header.js
@@ -17,40 +17,44 @@
 (function() {
   'use strict';
 
-  Polymer({
-    is: 'gr-user-header',
+  class GrUserHeader extends Polymer.GestureEventListeners(
+      Polymer.LegacyElementMixin(
+          Polymer.Element)) {
+    static get is() { return 'gr-user-header'; }
 
-    properties: {
+    static get properties() {
+      return {
       /** @type {?String} */
-      userId: {
-        type: String,
-        observer: '_accountChanged',
-      },
+        userId: {
+          type: String,
+          observer: '_accountChanged',
+        },
 
-      showDashboardLink: {
-        type: Boolean,
-        value: false,
-      },
+        showDashboardLink: {
+          type: Boolean,
+          value: false,
+        },
 
-      loggedIn: {
-        type: Boolean,
-        value: false,
-      },
+        loggedIn: {
+          type: Boolean,
+          value: false,
+        },
 
-      /**
+        /**
        * @type {?{name: ?, email: ?, registered_on: ?}}
        */
-      _accountDetails: {
-        type: Object,
-        value: null,
-      },
+        _accountDetails: {
+          type: Object,
+          value: null,
+        },
 
-      /** @type {?String} */
-      _status: {
-        type: String,
-        value: null,
-      },
-    },
+        /** @type {?String} */
+        _status: {
+          type: String,
+          value: null,
+        },
+      };
+    }
 
     _accountChanged(userId) {
       if (!userId) {
@@ -65,19 +69,19 @@
       this.$.restAPI.getAccountStatus(userId).then(status => {
         this._status = status;
       });
-    },
+    }
 
     _computeDisplayClass(status) {
       return status ? ' ' : 'hide';
-    },
+    }
 
     _computeDetail(accountDetails, name) {
       return accountDetails ? accountDetails[name] : '';
-    },
+    }
 
     _computeStatusClass(accountDetails) {
       return this._computeDetail(accountDetails, 'status') ? '' : 'hide';
-    },
+    }
 
     _computeDashboardUrl(accountDetails) {
       if (!accountDetails) { return null; }
@@ -85,11 +89,13 @@
       const email = accountDetails.email;
       if (!id && !email ) { return null; }
       return Gerrit.Nav.getUrlForUserDashboard(id ? id : email);
-    },
+    }
 
     _computeDashboardLinkClass(showDashboardLink, loggedIn) {
       return showDashboardLink && loggedIn ?
         'dashboardLink' : 'dashboardLink hide';
-    },
-  });
+    }
+  }
+
+  customElements.define(GrUserHeader.is, GrUserHeader);
 })();
diff --git a/polygerrit-ui/app/elements/change-list/gr-user-header/gr-user-header_test.html b/polygerrit-ui/app/elements/change-list/gr-user-header/gr-user-header_test.html
index e837a5b..9e04ecd 100644
--- a/polygerrit-ui/app/elements/change-list/gr-user-header/gr-user-header_test.html
+++ b/polygerrit-ui/app/elements/change-list/gr-user-header/gr-user-header_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-user-header</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>
diff --git a/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.js b/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.js
index 0158627..2ce0d7d 100644
--- a/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.js
+++ b/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.js
@@ -192,9 +192,19 @@
   const AWAIT_CHANGE_ATTEMPTS = 5;
   const AWAIT_CHANGE_TIMEOUT_MS = 1000;
 
-  Polymer({
-    is: 'gr-change-actions',
-
+  /**
+    * @appliesMixin Gerrit.FireMixin
+    * @appliesMixin Gerrit.PatchSetMixin
+    * @appliesMixin Gerrit.RESTClientMixin
+    */
+  class GrChangeActions extends Polymer.mixinBehaviors( [
+    Gerrit.FireBehavior,
+    Gerrit.PatchSetBehavior,
+    Gerrit.RESTClientBehavior,
+  ], Polymer.GestureEventListeners(
+      Polymer.LegacyElementMixin(
+          Polymer.Element))) {
+    static get is() { return 'gr-change-actions'; }
     /**
      * Fired when the change should be reloaded.
      *
@@ -219,7 +229,15 @@
      * @event show-error
      */
 
-    properties: {
+    constructor() {
+      super();
+      this.ActionType = ActionType;
+      this.ChangeActions = ChangeActions;
+      this.RevisionActions = RevisionActions;
+    }
+
+    static get properties() {
+      return {
       /**
        * @type {{
        *    _number: number,
@@ -229,217 +247,214 @@
        *    subject: string,
        *  }}
        */
-      change: Object,
-      actions: {
-        type: Object,
-        value() { return {}; },
-      },
-      primaryActionKeys: {
-        type: Array,
-        value() {
-          return [
-            RevisionActions.SUBMIT,
-          ];
+        change: Object,
+        actions: {
+          type: Object,
+          value() { return {}; },
         },
-      },
-      disableEdit: {
-        type: Boolean,
-        value: false,
-      },
-      _hasKnownChainState: {
-        type: Boolean,
-        value: false,
-      },
-      _hideQuickApproveAction: {
-        type: Boolean,
-        value: false,
-      },
-      changeNum: String,
-      changeStatus: String,
-      commitNum: String,
-      hasParent: {
-        type: Boolean,
-        observer: '_computeChainState',
-      },
-      latestPatchNum: String,
-      commitMessage: {
-        type: String,
-        value: '',
-      },
-      /** @type {?} */
-      revisionActions: {
-        type: Object,
-        notify: true,
-        value() { return {}; },
-      },
-      // If property binds directly to [[revisionActions.submit]] it is not
-      // updated when revisionActions doesn't contain submit action.
-      /** @type {?} */
-      _revisionSubmitAction: {
-        type: Object,
-        computed: '_getSubmitAction(revisionActions)',
-      },
-      // If property binds directly to [[revisionActions.rebase]] it is not
-      // updated when revisionActions doesn't contain rebase action.
-      /** @type {?} */
-      _revisionRebaseAction: {
-        type: Object,
-        computed: '_getRebaseAction(revisionActions)',
-      },
-      privateByDefault: String,
+        primaryActionKeys: {
+          type: Array,
+          value() {
+            return [
+              RevisionActions.SUBMIT,
+            ];
+          },
+        },
+        disableEdit: {
+          type: Boolean,
+          value: false,
+        },
+        _hasKnownChainState: {
+          type: Boolean,
+          value: false,
+        },
+        _hideQuickApproveAction: {
+          type: Boolean,
+          value: false,
+        },
+        changeNum: String,
+        changeStatus: String,
+        commitNum: String,
+        hasParent: {
+          type: Boolean,
+          observer: '_computeChainState',
+        },
+        latestPatchNum: String,
+        commitMessage: {
+          type: String,
+          value: '',
+        },
+        /** @type {?} */
+        revisionActions: {
+          type: Object,
+          notify: true,
+          value() { return {}; },
+        },
+        // If property binds directly to [[revisionActions.submit]] it is not
+        // updated when revisionActions doesn't contain submit action.
+        /** @type {?} */
+        _revisionSubmitAction: {
+          type: Object,
+          computed: '_getSubmitAction(revisionActions)',
+        },
+        // If property binds directly to [[revisionActions.rebase]] it is not
+        // updated when revisionActions doesn't contain rebase action.
+        /** @type {?} */
+        _revisionRebaseAction: {
+          type: Object,
+          computed: '_getRebaseAction(revisionActions)',
+        },
+        privateByDefault: String,
 
-      _loading: {
-        type: Boolean,
-        value: true,
-      },
-      _actionLoadingMessage: {
-        type: String,
-        value: '',
-      },
-      _allActionValues: {
-        type: Array,
-        computed: '_computeAllActions(actions.*, revisionActions.*,' +
+        _loading: {
+          type: Boolean,
+          value: true,
+        },
+        _actionLoadingMessage: {
+          type: String,
+          value: '',
+        },
+        _allActionValues: {
+          type: Array,
+          computed: '_computeAllActions(actions.*, revisionActions.*,' +
             'primaryActionKeys.*, _additionalActions.*, change, ' +
             '_actionPriorityOverrides.*)',
-      },
-      _topLevelActions: {
-        type: Array,
-        computed: '_computeTopLevelActions(_allActionValues.*, ' +
-            '_hiddenActions.*, _overflowActions.*)',
-        observer: '_filterPrimaryActions',
-      },
-      _topLevelPrimaryActions: Array,
-      _topLevelSecondaryActions: Array,
-      _menuActions: {
-        type: Array,
-        computed: '_computeMenuActions(_allActionValues.*, _hiddenActions.*, ' +
-            '_overflowActions.*)',
-      },
-      _overflowActions: {
-        type: Array,
-        value() {
-          const value = [
-            {
-              type: ActionType.CHANGE,
-              key: ChangeActions.WIP,
-            },
-            {
-              type: ActionType.CHANGE,
-              key: ChangeActions.DELETE,
-            },
-            {
-              type: ActionType.REVISION,
-              key: RevisionActions.CHERRYPICK,
-            },
-            {
-              type: ActionType.CHANGE,
-              key: ChangeActions.MOVE,
-            },
-            {
-              type: ActionType.REVISION,
-              key: RevisionActions.DOWNLOAD,
-            },
-            {
-              type: ActionType.CHANGE,
-              key: ChangeActions.IGNORE,
-            },
-            {
-              type: ActionType.CHANGE,
-              key: ChangeActions.UNIGNORE,
-            },
-            {
-              type: ActionType.CHANGE,
-              key: ChangeActions.REVIEWED,
-            },
-            {
-              type: ActionType.CHANGE,
-              key: ChangeActions.UNREVIEWED,
-            },
-            {
-              type: ActionType.CHANGE,
-              key: ChangeActions.PRIVATE,
-            },
-            {
-              type: ActionType.CHANGE,
-              key: ChangeActions.PRIVATE_DELETE,
-            },
-            {
-              type: ActionType.CHANGE,
-              key: ChangeActions.FOLLOW_UP,
-            },
-          ];
-          return value;
         },
-      },
-      _actionPriorityOverrides: {
-        type: Array,
-        value() { return []; },
-      },
-      _additionalActions: {
-        type: Array,
-        value() { return []; },
-      },
-      _hiddenActions: {
-        type: Array,
-        value() { return []; },
-      },
-      _disabledMenuActions: {
-        type: Array,
-        value() { return []; },
-      },
-      // editPatchsetLoaded == "does the current selected patch range have
-      // 'edit' as one of either basePatchNum or patchNum".
-      editPatchsetLoaded: {
-        type: Boolean,
-        value: false,
-      },
-      // editMode == "is edit mode enabled in the file list".
-      editMode: {
-        type: Boolean,
-        value: false,
-      },
-      editBasedOnCurrentPatchSet: {
-        type: Boolean,
-        value: true,
-      },
-    },
+        _topLevelActions: {
+          type: Array,
+          computed: '_computeTopLevelActions(_allActionValues.*, ' +
+            '_hiddenActions.*, _overflowActions.*)',
+          observer: '_filterPrimaryActions',
+        },
+        _topLevelPrimaryActions: Array,
+        _topLevelSecondaryActions: Array,
+        _menuActions: {
+          type: Array,
+          computed: '_computeMenuActions(_allActionValues.*, ' +
+            '_hiddenActions.*, _overflowActions.*)',
+        },
+        _overflowActions: {
+          type: Array,
+          value() {
+            const value = [
+              {
+                type: ActionType.CHANGE,
+                key: ChangeActions.WIP,
+              },
+              {
+                type: ActionType.CHANGE,
+                key: ChangeActions.DELETE,
+              },
+              {
+                type: ActionType.REVISION,
+                key: RevisionActions.CHERRYPICK,
+              },
+              {
+                type: ActionType.CHANGE,
+                key: ChangeActions.MOVE,
+              },
+              {
+                type: ActionType.REVISION,
+                key: RevisionActions.DOWNLOAD,
+              },
+              {
+                type: ActionType.CHANGE,
+                key: ChangeActions.IGNORE,
+              },
+              {
+                type: ActionType.CHANGE,
+                key: ChangeActions.UNIGNORE,
+              },
+              {
+                type: ActionType.CHANGE,
+                key: ChangeActions.REVIEWED,
+              },
+              {
+                type: ActionType.CHANGE,
+                key: ChangeActions.UNREVIEWED,
+              },
+              {
+                type: ActionType.CHANGE,
+                key: ChangeActions.PRIVATE,
+              },
+              {
+                type: ActionType.CHANGE,
+                key: ChangeActions.PRIVATE_DELETE,
+              },
+              {
+                type: ActionType.CHANGE,
+                key: ChangeActions.FOLLOW_UP,
+              },
+            ];
+            return value;
+          },
+        },
+        _actionPriorityOverrides: {
+          type: Array,
+          value() { return []; },
+        },
+        _additionalActions: {
+          type: Array,
+          value() { return []; },
+        },
+        _hiddenActions: {
+          type: Array,
+          value() { return []; },
+        },
+        _disabledMenuActions: {
+          type: Array,
+          value() { return []; },
+        },
+        // editPatchsetLoaded == "does the current selected patch range have
+        // 'edit' as one of either basePatchNum or patchNum".
+        editPatchsetLoaded: {
+          type: Boolean,
+          value: false,
+        },
+        // editMode == "is edit mode enabled in the file list".
+        editMode: {
+          type: Boolean,
+          value: false,
+        },
+        editBasedOnCurrentPatchSet: {
+          type: Boolean,
+          value: true,
+        },
+      };
+    }
 
-    ActionType,
-    ChangeActions,
-    RevisionActions,
-
-    behaviors: [
-      Gerrit.FireBehavior,
-      Gerrit.PatchSetBehavior,
-      Gerrit.RESTClientBehavior,
-    ],
-
-    observers: [
-      '_actionsChanged(actions.*, revisionActions.*, _additionalActions.*)',
-      '_changeChanged(change)',
-      '_editStatusChanged(editMode, editPatchsetLoaded, ' +
+    static get observers() {
+      return [
+        '_actionsChanged(actions.*, revisionActions.*, _additionalActions.*)',
+        '_changeChanged(change)',
+        '_editStatusChanged(editMode, editPatchsetLoaded, ' +
           'editBasedOnCurrentPatchSet, disableEdit, actions.*, change.*)',
-    ],
+      ];
+    }
 
-    listeners: {
-      'fullscreen-overlay-opened': '_handleHideBackgroundContent',
-      'fullscreen-overlay-closed': '_handleShowBackgroundContent',
-    },
+    created() {
+      super.created();
+      this.addEventListener('fullscreen-overlay-opened',
+          () => this._handleHideBackgroundContent());
+      this.addEventListener('fullscreen-overlay-closed',
+          () => this._handleShowBackgroundContent());
+    }
 
     ready() {
+      super.ready();
       this.$.jsAPI.addElement(this.$.jsAPI.Element.CHANGE_ACTIONS, this);
       this._handleLoadingComplete();
-    },
+    }
 
     _getSubmitAction(revisionActions) {
       return this._getRevisionAction(revisionActions, 'submit', null);
-    },
+    }
 
     _getRebaseAction(revisionActions) {
       return this._getRevisionAction(revisionActions, 'rebase',
           {rebaseOnCurrent: null}
       );
-    },
+    }
 
     _getRevisionAction(revisionActions, actionName, emptyActionValue) {
       if (!revisionActions) {
@@ -451,7 +466,7 @@
         return emptyActionValue;
       }
       return revisionActions[actionName];
-    },
+    }
 
     reload() {
       if (!this.changeNum || !this.latestPatchNum) {
@@ -469,11 +484,11 @@
         this._loading = false;
         throw err;
       });
-    },
+    }
 
     _handleLoadingComplete() {
       Gerrit.awaitPluginsLoaded().then(() => this._loading = false);
-    },
+    }
 
     _updateRebaseAction(revisionActions) {
       if (revisionActions && revisionActions.rebase) {
@@ -485,11 +500,11 @@
         this._parentIsCurrent = true;
       }
       return revisionActions;
-    },
+    }
 
     _changeChanged() {
       this.reload();
-    },
+    }
 
     addActionButton(type, label) {
       if (type !== ActionType.CHANGE && type !== ActionType.REVISION) {
@@ -504,7 +519,7 @@
       };
       this.push('_additionalActions', action);
       return action.__key;
-    },
+    }
 
     removeActionButton(key) {
       const idx = this._indexOfActionButtonWithKey(key);
@@ -512,7 +527,7 @@
         return;
       }
       this.splice('_additionalActions', idx, 1);
-    },
+    }
 
     setActionButtonProp(key, prop, value) {
       this.set([
@@ -520,7 +535,7 @@
         this._indexOfActionButtonWithKey(key),
         prop,
       ], value);
-    },
+    }
 
     setActionOverflow(type, key, overflow) {
       if (type !== ActionType.CHANGE && type !== ActionType.REVISION) {
@@ -537,7 +552,7 @@
       } else if (overflow) {
         this.push('_overflowActions', action);
       }
-    },
+    }
 
     setActionPriority(type, key, priority) {
       if (type !== ActionType.CHANGE && type !== ActionType.REVISION) {
@@ -556,7 +571,7 @@
       } else {
         this.push('_actionPriorityOverrides', action);
       }
-    },
+    }
 
     setActionHidden(type, key, hidden) {
       if (type !== ActionType.CHANGE && type !== ActionType.REVISION) {
@@ -569,7 +584,7 @@
       } else if (!hidden && idx !== -1) {
         this.splice('_hiddenActions', idx, 1);
       }
-    },
+    }
 
     getActionDetails(action) {
       if (this.revisionActions[action]) {
@@ -577,7 +592,7 @@
       } else if (this.actions[action]) {
         return this.actions[action];
       }
-    },
+    }
 
     _indexOfActionButtonWithKey(key) {
       for (let i = 0; i < this._additionalActions.length; i++) {
@@ -586,20 +601,20 @@
         }
       }
       return -1;
-    },
+    }
 
     _getRevisionActions() {
       return this.$.restAPI.getChangeRevisionActions(this.changeNum,
           this.latestPatchNum);
-    },
+    }
 
     _shouldHideActions(actions, loading) {
       return loading || !actions || !actions.base || !actions.base.length;
-    },
+    }
 
     _keyCount(changeRecord) {
       return Object.keys((changeRecord && changeRecord.base) || {}).length;
-    },
+    }
 
     _actionsChanged(actionsChangeRecord, revisionActionsChangeRecord,
         additionalActionsChangeRecord) {
@@ -626,7 +641,7 @@
           this.set('revisionActions.download', DOWNLOAD_ACTION);
         }
       }
-    },
+    }
 
     /**
        * @param {string=} actionName
@@ -638,7 +653,7 @@
         // see https://github.com/Polymer/polymer/issues/2631
         this.notifyPath('actions.' + actionName, false);
       }
-    },
+    }
 
     _editStatusChanged(editMode, editPatchsetLoaded,
         editBasedOnCurrentPatchSet, disableEdit) {
@@ -705,13 +720,13 @@
         // Remove edit button.
         this._deleteAndNotify('edit');
       }
-    },
+    }
 
     _getValuesFor(obj) {
       return Object.keys(obj).map(key => {
         return obj[key];
       });
-    },
+    }
 
     _getLabelStatus(label) {
       if (label.approved) {
@@ -723,7 +738,7 @@
       } else {
         return LabelStatus.NEED;
       }
-    },
+    }
 
     /**
      * Get highest score for last missing permitted label for current change.
@@ -771,7 +786,7 @@
         }
       }
       return null;
-    },
+    }
 
     hideQuickApproveAction() {
       this._topLevelSecondaryActions =
@@ -779,7 +794,7 @@
           return sa.key !== QUICK_APPROVE_ACTION.key;
         });
       this._hideQuickApproveAction = true;
-    },
+    }
 
     _getQuickApproveAction() {
       if (this._hideQuickApproveAction) {
@@ -798,7 +813,7 @@
       review.labels[approval.label] = approval.score;
       action.payload = review;
       return action;
-    },
+    }
 
     _getActionValues(actionsChangeRecord, primariesChangeRecord,
         additionalActionsChangeRecord, type) {
@@ -843,7 +858,7 @@
         return Object.assign({}, a);
       });
       return result.concat(additionalActions).concat(pluginActions);
-    },
+    }
 
     _populateActionUrl(action) {
       const patchNum =
@@ -851,7 +866,7 @@
       this.$.restAPI.getChangeActionURL(
           this.changeNum, patchNum, '/' + action.__key)
           .then(url => action.__url = url);
-    },
+    }
 
     /**
      * Given a change action, return a display label that uses the appropriate
@@ -867,7 +882,7 @@
       }
       // Otherwise, just map the name to sentence case.
       return this._toSentenceCase(action.label);
-    },
+    }
 
     /**
      * Capitalize the first letter and lowecase all others.
@@ -877,16 +892,16 @@
     _toSentenceCase(s) {
       if (!s.length) { return ''; }
       return s[0].toUpperCase() + s.slice(1).toLowerCase();
-    },
+    }
 
     _computeLoadingLabel(action) {
       return ActionLoadingLabels[action] || 'Working...';
-    },
+    }
 
     _canSubmitChange() {
       return this.$.jsAPI.canSubmitChange(this.change,
           this._getRevision(this.change, this.latestPatchNum));
-    },
+    }
 
     _getRevision(change, patchNum) {
       for (const rev of Object.values(change.revisions)) {
@@ -895,24 +910,24 @@
         }
       }
       return null;
-    },
+    }
 
     _modifyRevertMsg() {
       return this.$.jsAPI.modifyRevertMsg(this.change,
           this.$.confirmRevertDialog.message, this.commitMessage);
-    },
+    }
 
     showRevertDialog() {
       this.$.confirmRevertDialog.populateRevertMessage(
           this.commitMessage, this.change.current_revision);
       this.$.confirmRevertDialog.message = this._modifyRevertMsg();
       this._showActionDialog(this.$.confirmRevertDialog);
-    },
+    }
 
     _modifyRevertSubmissionMsg() {
       return this.$.jsAPI.modifyRevertSubmissionMsg(this.change,
           this.$.confirmRevertSubmissionDialog.message, this.commitMessage);
-    },
+    }
 
     showRevertSubmissionDialog() {
       this.$.confirmRevertSubmissionDialog.populateRevertSubmissionMessage(
@@ -920,7 +935,7 @@
       this.$.confirmRevertSubmissionDialog.message =
           this._modifyRevertSubmissionMsg();
       this._showActionDialog(this.$.confirmRevertSubmissionDialog);
-    },
+    }
 
     _handleActionTap(e) {
       e.preventDefault();
@@ -938,7 +953,7 @@
       }
       const type = el.getAttribute('data-action-type');
       this._handleAction(type, key);
-    },
+    }
 
     _handleOveflowItemTap(e) {
       e.preventDefault();
@@ -950,7 +965,7 @@
         return;
       }
       this._handleAction(e.detail.action.__type, e.detail.action.__key);
-    },
+    }
 
     _handleAction(type, key) {
       this.$.reporting.reportInteraction(`${type}-${key}`);
@@ -964,7 +979,7 @@
         default:
           this._fireAction(this._prependSlash(key), this.actions[key], false);
       }
-    },
+    }
 
     _handleChangeAction(key) {
       let action;
@@ -1015,7 +1030,7 @@
         default:
           this._fireAction(this._prependSlash(key), this.actions[key], false);
       }
-    },
+    }
 
     _handleRevisionAction(key) {
       switch (key) {
@@ -1037,11 +1052,11 @@
           this._fireAction(this._prependSlash(key),
               this.revisionActions[key], true);
       }
-    },
+    }
 
     _prependSlash(key) {
       return key === '/' ? key : `/${key}`;
-    },
+    }
 
     /**
      * _hasKnownChainState set to true true if hasParent is defined (can be
@@ -1049,25 +1064,25 @@
      */
     _computeChainState(hasParent) {
       this._hasKnownChainState = true;
-    },
+    }
 
     _calculateDisabled(action, hasKnownChainState) {
       if (action.__key === 'rebase' && hasKnownChainState === false) {
         return true;
       }
       return !action.enabled;
-    },
+    }
 
     _handleConfirmDialogCancel() {
       this._hideAllDialogs();
-    },
+    }
 
     _hideAllDialogs() {
       const dialogEls =
           Polymer.dom(this.root).querySelectorAll('.confirmDialog');
       for (const dialogEl of dialogEls) { dialogEl.hidden = true; }
       this.$.overlay.close();
-    },
+    }
 
     _handleRebaseConfirm(e) {
       const el = this.$.confirmRebase;
@@ -1075,15 +1090,15 @@
       this.$.overlay.close();
       el.hidden = true;
       this._fireAction('/rebase', this.revisionActions.rebase, true, payload);
-    },
+    }
 
     _handleCherrypickConfirm() {
       this._handleCherryPickRestApi(false);
-    },
+    }
 
     _handleCherrypickConflictConfirm() {
       this._handleCherryPickRestApi(true);
-    },
+    }
 
     _handleCherryPickRestApi(conflicts) {
       const el = this.$.confirmCherrypick;
@@ -1108,7 +1123,7 @@
             allow_conflicts: conflicts,
           }
       );
-    },
+    }
 
     _handleMoveConfirm() {
       const el = this.$.confirmMove;
@@ -1127,7 +1142,7 @@
             message: el.message,
           }
       );
-    },
+    }
 
     _handleRevertDialogConfirm() {
       const el = this.$.confirmRevertDialog;
@@ -1135,7 +1150,7 @@
       el.hidden = true;
       this._fireAction('/revert', this.actions.revert, false,
           {message: el.message});
-    },
+    }
 
     _handleRevertSubmissionDialogConfirm() {
       const el = this.$.confirmRevertSubmissionDialog;
@@ -1143,7 +1158,7 @@
       el.hidden = true;
       this._fireAction('/revert_submission', this.actions.revert_submission,
           false, {message: el.message});
-    },
+    }
 
     _handleAbandonDialogConfirm() {
       const el = this.$.confirmAbandonDialog;
@@ -1151,38 +1166,38 @@
       el.hidden = true;
       this._fireAction('/abandon', this.actions.abandon, false,
           {message: el.message});
-    },
+    }
 
     _handleCreateFollowUpChange() {
       this.$.createFollowUpChange.handleCreateChange();
       this._handleCloseCreateFollowUpChange();
-    },
+    }
 
     _handleCloseCreateFollowUpChange() {
       this.$.overlay.close();
-    },
+    }
 
     _handleDeleteConfirm() {
       this._fireAction('/', this.actions[ChangeActions.DELETE], false);
-    },
+    }
 
     _handleDeleteEditConfirm() {
       this._hideAllDialogs();
 
       this._fireAction('/edit', this.actions.deleteEdit, false);
-    },
+    }
 
     _handleSubmitConfirm() {
       if (!this._canSubmitChange()) { return; }
       this._hideAllDialogs();
       this._fireAction('/submit', this.revisionActions.submit, true);
-    },
+    }
 
     _getActionOverflowIndex(type, key) {
       return this._overflowActions.findIndex(action => {
         return action.type === type && action.key === key;
       });
-    },
+    }
 
     _setLoadingOnButtonWithKey(type, key) {
       this._actionLoadingMessage = this._computeLoadingLabel(key);
@@ -1205,7 +1220,7 @@
         buttonEl.removeAttribute('loading');
         buttonEl.disabled = false;
       }.bind(this);
-    },
+    }
 
     /**
      * @param {string} endpoint
@@ -1219,7 +1234,7 @@
 
       this._send(action.method, opt_payload, endpoint, revAction, cleanupFn,
           action).then(this._handleResponse.bind(this, action));
-    },
+    }
 
     _showActionDialog(dialog) {
       this._hideAllDialogs();
@@ -1230,7 +1245,7 @@
           dialog.resetFocus();
         }
       });
-    },
+    }
 
     // TODO(rmistry): Redo this after
     // https://bugs.chromium.org/p/gerrit/issues/detail?id=4671 is resolved.
@@ -1238,7 +1253,7 @@
       const labels = this.$.jsAPI.getLabelValuesPostRevert(this.change);
       if (!labels) { return Promise.resolve(); }
       return this.$.restAPI.saveChangeReview(newChangeId, 'current', {labels});
-    },
+    }
 
     _handleResponse(action, response) {
       if (!response) { return; }
@@ -1273,7 +1288,7 @@
             break;
         }
       });
-    },
+    }
 
     _handleResponseError(action, response, body) {
       if (action && action.__key === RevisionActions.CHERRYPICK) {
@@ -1290,7 +1305,7 @@
           throw Error(errText);
         }
       });
-    },
+    }
 
     /**
      * @param {string} method
@@ -1333,58 +1348,58 @@
                   return response;
                 });
           });
-    },
+    }
 
     _handleAbandonTap() {
       this._showActionDialog(this.$.confirmAbandonDialog);
-    },
+    }
 
     _handleCherrypickTap() {
       this.$.confirmCherrypick.branch = '';
       this._showActionDialog(this.$.confirmCherrypick);
-    },
+    }
 
     _handleMoveTap() {
       this.$.confirmMove.branch = '';
       this.$.confirmMove.message = '';
       this._showActionDialog(this.$.confirmMove);
-    },
+    }
 
     _handleDownloadTap() {
       this.fire('download-tap', null, {bubbles: false});
-    },
+    }
 
     _handleDeleteTap() {
       this._showActionDialog(this.$.confirmDeleteDialog);
-    },
+    }
 
     _handleDeleteEditTap() {
       this._showActionDialog(this.$.confirmDeleteEditDialog);
-    },
+    }
 
     _handleFollowUpTap() {
       this._showActionDialog(this.$.createFollowUpDialog);
-    },
+    }
 
     _handleWipTap() {
       this._fireAction('/wip', this.actions.wip, false);
-    },
+    }
 
     _handlePublishEditTap() {
       this._fireAction('/edit:publish', this.actions.publishEdit, false);
-    },
+    }
 
     _handleRebaseEditTap() {
       this._fireAction('/edit:rebase', this.actions.rebaseEdit, false);
-    },
+    }
 
     _handleHideBackgroundContent() {
       this.$.mainContent.classList.add('overlayOpen');
-    },
+    }
 
     _handleShowBackgroundContent() {
       this.$.mainContent.classList.remove('overlayOpen');
-    },
+    }
 
     /**
      * Merge sources of change actions into a single ordered array of action
@@ -1427,7 +1442,7 @@
             }
             return action;
           });
-    },
+    }
 
     _getActionPriority(action) {
       if (action.__type && action.__key) {
@@ -1449,7 +1464,7 @@
         return ActionPriority.REVISION;
       }
       return ActionPriority.DEFAULT;
-    },
+    }
 
     /**
      * Sort comparator to define the order of change actions.
@@ -1463,7 +1478,7 @@
       } else {
         return priorityDelta;
       }
-    },
+    }
 
     _computeTopLevelActions(actionRecord, hiddenActionsRecord) {
       const hiddenActions = hiddenActionsRecord.base || [];
@@ -1471,14 +1486,14 @@
         const overflow = this._getActionOverflowIndex(a.__type, a.__key) !== -1;
         return !(overflow || hiddenActions.includes(a.__key));
       });
-    },
+    }
 
     _filterPrimaryActions(_topLevelActions) {
       this._topLevelPrimaryActions = _topLevelActions.filter(action =>
         action.__primary);
       this._topLevelSecondaryActions = _topLevelActions.filter(action =>
         !action.__primary);
-    },
+    }
 
     _computeMenuActions(actionRecord, hiddenActionsRecord) {
       const hiddenActions = hiddenActionsRecord.base || [];
@@ -1495,7 +1510,7 @@
           tooltip: action.title,
         };
       });
-    },
+    }
 
     /**
      * Occasionally, a change created by a change action is not yet knwon to the
@@ -1529,22 +1544,24 @@
         };
         check();
       });
-    },
+    }
 
     _handleEditTap() {
       this.dispatchEvent(new CustomEvent('edit-tap', {bubbles: false}));
-    },
+    }
 
     _handleStopEditTap() {
       this.dispatchEvent(new CustomEvent('stop-edit-tap', {bubbles: false}));
-    },
+    }
 
     _computeHasTooltip(title) {
       return !!title;
-    },
+    }
 
     _computeHasIcon(action) {
       return action.icon ? '' : 'hidden';
-    },
-  });
+    }
+  }
+
+  customElements.define(GrChangeActions.is, GrChangeActions);
 })();
diff --git a/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions_test.html b/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions_test.html
index 862b4eb..a10f028 100644
--- a/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions_test.html
+++ b/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-change-actions</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>
diff --git a/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata-it_test.html b/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata-it_test.html
index b8bea9ca..8d85dce 100644
--- a/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata-it_test.html
+++ b/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata-it_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-change-metadata</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>
diff --git a/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.js b/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.js
index 310a1a6..e0d9676 100644
--- a/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.js
+++ b/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.js
@@ -48,105 +48,111 @@
     TRUSTED: 'TRUSTED',
   };
 
-  Polymer({
-    is: 'gr-change-metadata',
-
+  /**
+    * @appliesMixin Gerrit.RESTClientMixin
+    */
+  class GrChangeMetadata extends Polymer.mixinBehaviors( [
+    Gerrit.RESTClientBehavior,
+  ], Polymer.GestureEventListeners(
+      Polymer.LegacyElementMixin(
+          Polymer.Element))) {
+    static get is() { return 'gr-change-metadata'; }
     /**
      * Fired when the change topic is changed.
      *
      * @event topic-changed
      */
 
-    properties: {
+    static get properties() {
+      return {
       /** @type {?} */
-      change: Object,
-      labels: {
-        type: Object,
-        notify: true,
-      },
-      account: Object,
-      /** @type {?} */
-      revision: Object,
-      commitInfo: Object,
-      _mutable: {
-        type: Boolean,
-        computed: '_computeIsMutable(account)',
-      },
-      /** @type {?} */
-      serverConfig: Object,
-      parentIsCurrent: Boolean,
-      _notCurrentMessage: {
-        type: String,
-        value: NOT_CURRENT_MESSAGE,
-        readOnly: true,
-      },
-      _topicReadOnly: {
-        type: Boolean,
-        computed: '_computeTopicReadOnly(_mutable, change)',
-      },
-      _hashtagReadOnly: {
-        type: Boolean,
-        computed: '_computeHashtagReadOnly(_mutable, change)',
-      },
-      /**
+        change: Object,
+        labels: {
+          type: Object,
+          notify: true,
+        },
+        account: Object,
+        /** @type {?} */
+        revision: Object,
+        commitInfo: Object,
+        _mutable: {
+          type: Boolean,
+          computed: '_computeIsMutable(account)',
+        },
+        /** @type {?} */
+        serverConfig: Object,
+        parentIsCurrent: Boolean,
+        _notCurrentMessage: {
+          type: String,
+          value: NOT_CURRENT_MESSAGE,
+          readOnly: true,
+        },
+        _topicReadOnly: {
+          type: Boolean,
+          computed: '_computeTopicReadOnly(_mutable, change)',
+        },
+        _hashtagReadOnly: {
+          type: Boolean,
+          computed: '_computeHashtagReadOnly(_mutable, change)',
+        },
+        /**
        * @type {Gerrit.PushCertificateValidation}
        */
-      _pushCertificateValidation: {
-        type: Object,
-        computed: '_computePushCertificateValidation(serverConfig, change)',
-      },
-      _showRequirements: {
-        type: Boolean,
-        computed: '_computeShowRequirements(change)',
-      },
-
-      _assignee: Array,
-      _isWip: {
-        type: Boolean,
-        computed: '_computeIsWip(change)',
-      },
-      _newHashtag: String,
-
-      _settingTopic: {
-        type: Boolean,
-        value: false,
-      },
-
-      _currentParents: {
-        type: Array,
-        computed: '_computeParents(change)',
-      },
-
-      /** @type {?} */
-      _CHANGE_ROLE: {
-        type: Object,
-        readOnly: true,
-        value: {
-          OWNER: 'owner',
-          UPLOADER: 'uploader',
-          AUTHOR: 'author',
-          COMMITTER: 'committer',
+        _pushCertificateValidation: {
+          type: Object,
+          computed: '_computePushCertificateValidation(serverConfig, change)',
         },
-      },
-    },
+        _showRequirements: {
+          type: Boolean,
+          computed: '_computeShowRequirements(change)',
+        },
 
-    behaviors: [
-      Gerrit.RESTClientBehavior,
-    ],
+        _assignee: Array,
+        _isWip: {
+          type: Boolean,
+          computed: '_computeIsWip(change)',
+        },
+        _newHashtag: String,
 
-    observers: [
-      '_changeChanged(change)',
-      '_labelsChanged(change.labels)',
-      '_assigneeChanged(_assignee.*)',
-    ],
+        _settingTopic: {
+          type: Boolean,
+          value: false,
+        },
+
+        _currentParents: {
+          type: Array,
+          computed: '_computeParents(change)',
+        },
+
+        /** @type {?} */
+        _CHANGE_ROLE: {
+          type: Object,
+          readOnly: true,
+          value: {
+            OWNER: 'owner',
+            UPLOADER: 'uploader',
+            AUTHOR: 'author',
+            COMMITTER: 'committer',
+          },
+        },
+      };
+    }
+
+    static get observers() {
+      return [
+        '_changeChanged(change)',
+        '_labelsChanged(change.labels)',
+        '_assigneeChanged(_assignee.*)',
+      ];
+    }
 
     _labelsChanged(labels) {
       this.labels = Object.assign({}, labels) || null;
-    },
+    }
 
     _changeChanged(change) {
       this._assignee = change.assignee ? [change.assignee] : [];
-    },
+    }
 
     _assigneeChanged(assigneeRecord) {
       if (!this.change) { return; }
@@ -162,11 +168,11 @@
         this.set(['change', 'assignee'], undefined);
         this.$.restAPI.deleteAssignee(this.change._number);
       }
-    },
+    }
 
     _computeHideStrategy(change) {
       return !this.changeIsOpen(change);
-    },
+    }
 
     /**
      * @param {Object} commitInfo
@@ -184,15 +190,15 @@
             config: serverConfig,
           });
       return weblinks.length ? weblinks : null;
-    },
+    }
 
     _computeStrategy(change) {
       return SubmitTypeLabel[change.submit_type];
-    },
+    }
 
     _computeLabelNames(labels) {
       return Object.keys(labels).sort();
-    },
+    }
 
     _handleTopicChanged(e, topic) {
       const lastTopic = this.change.topic;
@@ -207,19 +213,19 @@
                   'topic-changed', {bubbles: true, composed: true}));
             }
           });
-    },
+    }
 
     _showAddTopic(changeRecord, settingTopic) {
       const hasTopic = !!changeRecord &&
           !!changeRecord.base && !!changeRecord.base.topic;
       return !hasTopic && !settingTopic;
-    },
+    }
 
     _showTopicChip(changeRecord, settingTopic) {
       const hasTopic = !!changeRecord &&
           !!changeRecord.base && !!changeRecord.base.topic;
       return hasTopic && !settingTopic;
-    },
+    }
 
     _handleHashtagChanged(e) {
       const lastHashtag = this.change.hashtag;
@@ -234,7 +240,7 @@
               'hashtag-changed', {bubbles: true, composed: true}));
         }
       });
-    },
+    }
 
     _computeTopicReadOnly(mutable, change) {
       return !mutable ||
@@ -242,7 +248,7 @@
           !change.actions ||
           !change.actions.topic ||
           !change.actions.topic.enabled;
-    },
+    }
 
     _computeHashtagReadOnly(mutable, change) {
       return !mutable ||
@@ -250,7 +256,7 @@
           !change.actions ||
           !change.actions.hashtags ||
           !change.actions.hashtags.enabled;
-    },
+    }
 
     _computeAssigneeReadOnly(mutable, change) {
       return !mutable ||
@@ -258,17 +264,17 @@
           !change.actions ||
           !change.actions.assignee ||
           !change.actions.assignee.enabled;
-    },
+    }
 
     _computeTopicPlaceholder(_topicReadOnly) {
       // Action items in Material Design are uppercase -- placeholder label text
       // is sentence case.
       return _topicReadOnly ? 'No topic' : 'ADD TOPIC';
-    },
+    }
 
     _computeHashtagPlaceholder(_hashtagReadOnly) {
       return _hashtagReadOnly ? '' : HASHTAG_ADD_MESSAGE;
-    },
+    }
 
     _computeShowRequirements(change) {
       if (change.status !== this.ChangeStatus.NEW) {
@@ -281,7 +287,7 @@
       const hasLabels = !!change.labels &&
           Object.keys(change.labels).length > 0;
       return hasRequirements || hasLabels || !!change.work_in_progress;
-    },
+    }
 
     /**
      * @return {?Gerrit.PushCertificateValidation} object representing data for
@@ -326,7 +332,7 @@
         default:
           throw new Error(`unknown certificate status: ${key.status}`);
       }
-    },
+    }
 
     _problems(msg, key) {
       if (!key || !key.problems || key.problems.length === 0) {
@@ -334,26 +340,26 @@
       }
 
       return [msg + ':'].concat(key.problems).join('\n');
-    },
+    }
 
     _computeProjectURL(project) {
       return Gerrit.Nav.getUrlForProjectChanges(project);
-    },
+    }
 
     _computeBranchURL(project, branch) {
       if (!this.change || !this.change.status) return '';
       return Gerrit.Nav.getUrlForBranch(branch, project,
           this.change.status == this.ChangeStatus.NEW ? 'open' :
             this.change.status.toLowerCase());
-    },
+    }
 
     _computeTopicURL(topic) {
       return Gerrit.Nav.getUrlForTopic(topic);
-    },
+    }
 
     _computeHashtagURL(hashtag) {
       return Gerrit.Nav.getUrlForHashtag(hashtag);
-    },
+    }
 
     _handleTopicRemoved(e) {
       const target = Polymer.dom(e).rootTarget;
@@ -367,7 +373,7 @@
         target.disabled = false;
         return;
       });
-    },
+    }
 
     _handleHashtagRemoved(e) {
       e.preventDefault();
@@ -382,15 +388,15 @@
             target.disabled = false;
             return;
           });
-    },
+    }
 
     _computeIsWip(change) {
       return !!change.work_in_progress;
-    },
+    }
 
     _computeShowRoleClass(change, role) {
       return this._getNonOwnerRole(change, role) ? '' : 'hideDisplay';
-    },
+    }
 
     /**
      * Get the user with the specified role on the change. Returns null if the
@@ -427,7 +433,7 @@
       }
 
       return null;
-    },
+    }
 
     _computeParents(change) {
       if (!change || !change.current_revision ||
@@ -436,11 +442,11 @@
         return undefined;
       }
       return change.revisions[change.current_revision].commit.parents;
-    },
+    }
 
     _computeParentsLabel(parents) {
       return parents && parents.length > 1 ? 'Parents' : 'Parent';
-    },
+    }
 
     _computeParentListClass(parents, parentIsCurrent) {
       // Undefined check for polymer 2
@@ -453,24 +459,26 @@
         parents && parents.length > 1 ? 'merge' : 'nonMerge',
         parentIsCurrent ? 'current' : 'notCurrent',
       ].join(' ');
-    },
+    }
 
     _computeIsMutable(account) {
       return !!Object.keys(account).length;
-    },
+    }
 
     editTopic() {
       if (this._topicReadOnly || this.change.topic) { return; }
       // Cannot use `this.$.ID` syntax because the element exists inside of a
       // dom-if.
       this.$$('.topicEditableLabel').open();
-    },
+    }
 
     _getReviewerSuggestionsProvider(change) {
       const provider = GrReviewerSuggestionsProvider.create(this.$.restAPI,
           change._number, Gerrit.SUGGESTIONS_PROVIDERS_USERS_TYPES.ANY);
       provider.init();
       return provider;
-    },
-  });
+    }
+  }
+
+  customElements.define(GrChangeMetadata.is, GrChangeMetadata);
 })();
diff --git a/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata_test.html b/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata_test.html
index 6f06fc8..148d917 100644
--- a/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata_test.html
+++ b/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-change-metadata</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>
diff --git a/polygerrit-ui/app/elements/change/gr-change-requirements/gr-change-requirements.js b/polygerrit-ui/app/elements/change/gr-change-requirements/gr-change-requirements.js
index dfdcd59..2e0332b 100644
--- a/polygerrit-ui/app/elements/change/gr-change-requirements/gr-change-requirements.js
+++ b/polygerrit-ui/app/elements/change/gr-change-requirements/gr-change-requirements.js
@@ -17,47 +17,54 @@
 (function() {
   'use strict';
 
-  Polymer({
-    is: 'gr-change-requirements',
+  /**
+    * @appliesMixin Gerrit.RESTClientMixin
+    */
+  class GrChangeRequirements extends Polymer.mixinBehaviors( [
+    Gerrit.RESTClientBehavior,
+  ], Polymer.GestureEventListeners(
+      Polymer.LegacyElementMixin(
+          Polymer.Element))) {
+    static get is() { return 'gr-change-requirements'; }
 
-    properties: {
+    static get properties() {
+      return {
       /** @type {?} */
-      change: Object,
-      account: Object,
-      mutable: Boolean,
-      _requirements: {
-        type: Array,
-        computed: '_computeRequirements(change)',
-      },
-      _requiredLabels: {
-        type: Array,
-        value: () => [],
-      },
-      _optionalLabels: {
-        type: Array,
-        value: () => [],
-      },
-      _showWip: {
-        type: Boolean,
-        computed: '_computeShowWip(change)',
-      },
-      _showOptionalLabels: {
-        type: Boolean,
-        value: true,
-      },
-    },
+        change: Object,
+        account: Object,
+        mutable: Boolean,
+        _requirements: {
+          type: Array,
+          computed: '_computeRequirements(change)',
+        },
+        _requiredLabels: {
+          type: Array,
+          value: () => [],
+        },
+        _optionalLabels: {
+          type: Array,
+          value: () => [],
+        },
+        _showWip: {
+          type: Boolean,
+          computed: '_computeShowWip(change)',
+        },
+        _showOptionalLabels: {
+          type: Boolean,
+          value: true,
+        },
+      };
+    }
 
-    behaviors: [
-      Gerrit.RESTClientBehavior,
-    ],
-
-    observers: [
-      '_computeLabels(change.labels.*)',
-    ],
+    static get observers() {
+      return [
+        '_computeLabels(change.labels.*)',
+      ];
+    }
 
     _computeShowWip(change) {
       return change.work_in_progress;
-    },
+    }
 
     _computeRequirements(change) {
       const _requirements = [];
@@ -78,15 +85,15 @@
       }
 
       return _requirements;
-    },
+    }
 
     _computeRequirementClass(requirementStatus) {
       return requirementStatus ? 'approved' : '';
-    },
+    }
 
     _computeRequirementIcon(requirementStatus) {
       return requirementStatus ? 'gr-icons:check' : 'gr-icons:hourglass';
-    },
+    }
 
     _computeLabels(labelsRecord) {
       const labels = labelsRecord.base;
@@ -103,7 +110,7 @@
 
         this.push(path, {label, icon, style, labelInfo});
       }
-    },
+    }
 
     /**
      * @param {Object} labelInfo
@@ -114,7 +121,7 @@
       if (labelInfo.approved) { return 'gr-icons:check'; }
       if (labelInfo.rejected) { return 'gr-icons:close'; }
       return 'gr-icons:hourglass';
-    },
+    }
 
     /**
      * @param {Object} labelInfo
@@ -123,28 +130,30 @@
       if (labelInfo.approved) { return 'approved'; }
       if (labelInfo.rejected) { return 'rejected'; }
       return '';
-    },
+    }
 
     _computeShowOptional(optionalFieldsRecord) {
       return optionalFieldsRecord.base.length ? '' : 'hidden';
-    },
+    }
 
     _computeLabelValue(value) {
       return (value > 0 ? '+' : '') + value;
-    },
+    }
 
     _computeShowHideIcon(showOptionalLabels) {
       return showOptionalLabels ?
         'gr-icons:expand-less' :
         'gr-icons:expand-more';
-    },
+    }
 
     _computeSectionClass(show) {
       return show ? '' : 'hidden';
-    },
+    }
 
     _handleShowHide(e) {
       this._showOptionalLabels = !this._showOptionalLabels;
-    },
-  });
+    }
+  }
+
+  customElements.define(GrChangeRequirements.is, GrChangeRequirements);
 })();
diff --git a/polygerrit-ui/app/elements/change/gr-change-requirements/gr-change-requirements_test.html b/polygerrit-ui/app/elements/change/gr-change-requirements/gr-change-requirements_test.html
index 2ceac39..242fe2c 100644
--- a/polygerrit-ui/app/elements/change/gr-change-requirements/gr-change-requirements_test.html
+++ b/polygerrit-ui/app/elements/change/gr-change-requirements/gr-change-requirements_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-change-requirements</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>
diff --git a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.js b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.js
index ff63a32..5de378b 100644
--- a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.js
+++ b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.js
@@ -63,9 +63,21 @@
   const CHANGE_RELOAD_TIMING_LABEL = 'ChangeReloaded';
   const SEND_REPLY_TIMING_LABEL = 'SendReply';
 
-  Polymer({
-    is: 'gr-change-view',
-
+  /**
+    * @appliesMixin Gerrit.FireMixin
+    * @appliesMixin Gerrit.KeyboardShortcutMixin
+    * @appliesMixin Gerrit.PatchSetMixin
+    * @appliesMixin Gerrit.RESTClientMixin
+    */
+  class GrChangeView extends Polymer.mixinBehaviors( [
+    Gerrit.FireBehavior,
+    Gerrit.KeyboardShortcutBehavior,
+    Gerrit.PatchSetBehavior,
+    Gerrit.RESTClientBehavior,
+  ], Polymer.GestureEventListeners(
+      Polymer.LegacyElementMixin(
+          Polymer.Element))) {
+    static get is() { return 'gr-change-view'; }
     /**
      * Fired when the title of the page should change.
      *
@@ -84,230 +96,215 @@
      * @event show-auth-required
      */
 
-    properties: {
+    static get properties() {
+      return {
       /**
        * URL params passed from the router.
        */
-      params: {
-        type: Object,
-        observer: '_paramsChanged',
-      },
-      /** @type {?} */
-      viewState: {
-        type: Object,
-        notify: true,
-        value() { return {}; },
-        observer: '_viewStateChanged',
-      },
-      backPage: String,
-      hasParent: Boolean,
-      keyEventTarget: {
-        type: Object,
-        value() { return document.body; },
-      },
-      disableEdit: {
-        type: Boolean,
-        value: false,
-      },
-      disableDiffPrefs: {
-        type: Boolean,
-        value: false,
-      },
-      _diffPrefsDisabled: {
-        type: Boolean,
-        computed: '_computeDiffPrefsDisabled(disableDiffPrefs, _loggedIn)',
-      },
-      _commentThreads: Array,
-      /** @type {?} */
-      _serverConfig: {
-        type: Object,
-        observer: '_startUpdateCheckTimer',
-      },
-      _diffPrefs: Object,
-      _numFilesShown: {
-        type: Number,
-        value: DEFAULT_NUM_FILES_SHOWN,
-        observer: '_numFilesShownChanged',
-      },
-      _account: {
-        type: Object,
-        value: {},
-      },
-      _prefs: Object,
-      /** @type {?} */
-      _changeComments: Object,
-      _canStartReview: {
-        type: Boolean,
-        computed: '_computeCanStartReview(_change)',
-      },
-      _comments: Object,
-      /** @type {?} */
-      _change: {
-        type: Object,
-        observer: '_changeChanged',
-      },
-      _revisionInfo: {
-        type: Object,
-        computed: '_getRevisionInfo(_change)',
-      },
-      /** @type {?} */
-      _commitInfo: Object,
-      _currentRevision: {
-        type: Object,
-        computed: '_computeCurrentRevision(_change.current_revision, ' +
+        params: {
+          type: Object,
+          observer: '_paramsChanged',
+        },
+        /** @type {?} */
+        viewState: {
+          type: Object,
+          notify: true,
+          value() { return {}; },
+          observer: '_viewStateChanged',
+        },
+        backPage: String,
+        hasParent: Boolean,
+        keyEventTarget: {
+          type: Object,
+          value() { return document.body; },
+        },
+        disableEdit: {
+          type: Boolean,
+          value: false,
+        },
+        disableDiffPrefs: {
+          type: Boolean,
+          value: false,
+        },
+        _diffPrefsDisabled: {
+          type: Boolean,
+          computed: '_computeDiffPrefsDisabled(disableDiffPrefs, _loggedIn)',
+        },
+        _commentThreads: Array,
+        /** @type {?} */
+        _serverConfig: {
+          type: Object,
+          observer: '_startUpdateCheckTimer',
+        },
+        _diffPrefs: Object,
+        _numFilesShown: {
+          type: Number,
+          value: DEFAULT_NUM_FILES_SHOWN,
+          observer: '_numFilesShownChanged',
+        },
+        _account: {
+          type: Object,
+          value: {},
+        },
+        _prefs: Object,
+        /** @type {?} */
+        _changeComments: Object,
+        _canStartReview: {
+          type: Boolean,
+          computed: '_computeCanStartReview(_change)',
+        },
+        _comments: Object,
+        /** @type {?} */
+        _change: {
+          type: Object,
+          observer: '_changeChanged',
+        },
+        _revisionInfo: {
+          type: Object,
+          computed: '_getRevisionInfo(_change)',
+        },
+        /** @type {?} */
+        _commitInfo: Object,
+        _currentRevision: {
+          type: Object,
+          computed: '_computeCurrentRevision(_change.current_revision, ' +
             '_change.revisions)',
-      },
-      _files: Object,
-      _changeNum: String,
-      _diffDrafts: {
-        type: Object,
-        value() { return {}; },
-      },
-      _editingCommitMessage: {
-        type: Boolean,
-        value: false,
-      },
-      _hideEditCommitMessage: {
-        type: Boolean,
-        computed: '_computeHideEditCommitMessage(_loggedIn, ' +
+        },
+        _files: Object,
+        _changeNum: String,
+        _diffDrafts: {
+          type: Object,
+          value() { return {}; },
+        },
+        _editingCommitMessage: {
+          type: Boolean,
+          value: false,
+        },
+        _hideEditCommitMessage: {
+          type: Boolean,
+          computed: '_computeHideEditCommitMessage(_loggedIn, ' +
             '_editingCommitMessage, _change, _editMode)',
-      },
-      _diffAgainst: String,
-      /** @type {?string} */
-      _latestCommitMessage: {
-        type: String,
-        value: '',
-      },
-      _lineHeight: Number,
-      _changeIdCommitMessageError: {
-        type: String,
-        computed:
+        },
+        _diffAgainst: String,
+        /** @type {?string} */
+        _latestCommitMessage: {
+          type: String,
+          value: '',
+        },
+        _lineHeight: Number,
+        _changeIdCommitMessageError: {
+          type: String,
+          computed:
           '_computeChangeIdCommitMessageError(_latestCommitMessage, _change)',
-      },
-      /** @type {?} */
-      _patchRange: {
-        type: Object,
-      },
-      _filesExpanded: String,
-      _basePatchNum: String,
-      _selectedRevision: Object,
-      _currentRevisionActions: Object,
-      _allPatchSets: {
-        type: Array,
-        computed: 'computeAllPatchSets(_change, _change.revisions.*)',
-      },
-      _loggedIn: {
-        type: Boolean,
-        value: false,
-      },
-      _loading: Boolean,
-      /** @type {?} */
-      _projectConfig: Object,
-      _rebaseOnCurrent: Boolean,
-      _replyButtonLabel: {
-        type: String,
-        value: 'Reply',
-        computed: '_computeReplyButtonLabel(_diffDrafts.*, _canStartReview)',
-      },
-      _selectedPatchSet: String,
-      _shownFileCount: Number,
-      _initialLoadComplete: {
-        type: Boolean,
-        value: false,
-      },
-      _replyDisabled: {
-        type: Boolean,
-        value: true,
-        computed: '_computeReplyDisabled(_serverConfig)',
-      },
-      _changeStatus: {
-        type: String,
-        computed: 'changeStatusString(_change)',
-      },
-      _changeStatuses: {
-        type: String,
-        computed:
+        },
+        /** @type {?} */
+        _patchRange: {
+          type: Object,
+        },
+        _filesExpanded: String,
+        _basePatchNum: String,
+        _selectedRevision: Object,
+        _currentRevisionActions: Object,
+        _allPatchSets: {
+          type: Array,
+          computed: 'computeAllPatchSets(_change, _change.revisions.*)',
+        },
+        _loggedIn: {
+          type: Boolean,
+          value: false,
+        },
+        _loading: Boolean,
+        /** @type {?} */
+        _projectConfig: Object,
+        _rebaseOnCurrent: Boolean,
+        _replyButtonLabel: {
+          type: String,
+          value: 'Reply',
+          computed: '_computeReplyButtonLabel(_diffDrafts.*, _canStartReview)',
+        },
+        _selectedPatchSet: String,
+        _shownFileCount: Number,
+        _initialLoadComplete: {
+          type: Boolean,
+          value: false,
+        },
+        _replyDisabled: {
+          type: Boolean,
+          value: true,
+          computed: '_computeReplyDisabled(_serverConfig)',
+        },
+        _changeStatus: {
+          type: String,
+          computed: 'changeStatusString(_change)',
+        },
+        _changeStatuses: {
+          type: String,
+          computed:
           '_computeChangeStatusChips(_change, _mergeable, _submitEnabled)',
-      },
-      _commitCollapsed: {
-        type: Boolean,
-        value: true,
-      },
-      _relatedChangesCollapsed: {
-        type: Boolean,
-        value: true,
-      },
-      /** @type {?number} */
-      _updateCheckTimerHandle: Number,
-      _editMode: {
-        type: Boolean,
-        computed: '_computeEditMode(_patchRange.*, params.*)',
-      },
-      _showRelatedToggle: {
-        type: Boolean,
-        value: false,
-        observer: '_updateToggleContainerClass',
-      },
-      _parentIsCurrent: Boolean,
-      _submitEnabled: {
-        type: Boolean,
-        computed: '_isSubmitEnabled(_currentRevisionActions)',
-      },
+        },
+        _commitCollapsed: {
+          type: Boolean,
+          value: true,
+        },
+        _relatedChangesCollapsed: {
+          type: Boolean,
+          value: true,
+        },
+        /** @type {?number} */
+        _updateCheckTimerHandle: Number,
+        _editMode: {
+          type: Boolean,
+          computed: '_computeEditMode(_patchRange.*, params.*)',
+        },
+        _showRelatedToggle: {
+          type: Boolean,
+          value: false,
+          observer: '_updateToggleContainerClass',
+        },
+        _parentIsCurrent: Boolean,
+        _submitEnabled: {
+          type: Boolean,
+          computed: '_isSubmitEnabled(_currentRevisionActions)',
+        },
 
-      /** @type {?} */
-      _mergeable: {
-        type: Boolean,
-        value: undefined,
-      },
-      _showMessagesView: {
-        type: Boolean,
-        value: true,
-      },
-      _showFileTabContent: {
-        type: Boolean,
-        value: true,
-      },
-      /** @type {Array<string>} */
-      _dynamicTabHeaderEndpoints: {
-        type: Array,
-      },
-      _showPrimaryTabs: {
-        type: Boolean,
-        computed: '_computeShowPrimaryTabs(_dynamicTabHeaderEndpoints)',
-      },
-      /** @type {Array<string>} */
-      _dynamicTabContentEndpoints: {
-        type: Array,
-      },
-      _selectedFilesTabPluginEndpoint: {
-        type: String,
-      },
-    },
+        /** @type {?} */
+        _mergeable: {
+          type: Boolean,
+          value: undefined,
+        },
+        _showMessagesView: {
+          type: Boolean,
+          value: true,
+        },
+        _showFileTabContent: {
+          type: Boolean,
+          value: true,
+        },
+        /** @type {Array<string>} */
+        _dynamicTabHeaderEndpoints: {
+          type: Array,
+        },
+        _showPrimaryTabs: {
+          type: Boolean,
+          computed: '_computeShowPrimaryTabs(_dynamicTabHeaderEndpoints)',
+        },
+        /** @type {Array<string>} */
+        _dynamicTabContentEndpoints: {
+          type: Array,
+        },
+        _selectedFilesTabPluginEndpoint: {
+          type: String,
+        },
+      };
+    }
 
-    behaviors: [
-      Gerrit.FireBehavior,
-      Gerrit.KeyboardShortcutBehavior,
-      Gerrit.PatchSetBehavior,
-      Gerrit.RESTClientBehavior,
-    ],
-
-    listeners: {
-      'topic-changed': '_handleTopicChanged',
-      // When an overlay is opened in a mobile viewport, the overlay has a full
-      // screen view. When it has a full screen view, we do not want the
-      // background to be scrollable. This will eliminate background scroll by
-      // hiding most of the contents on the screen upon opening, and showing
-      // again upon closing.
-      'fullscreen-overlay-opened': '_handleHideBackgroundContent',
-      'fullscreen-overlay-closed': '_handleShowBackgroundContent',
-      'diff-comments-modified': '_handleReloadCommentThreads',
-    },
-
-    observers: [
-      '_labelsChanged(_change.labels.*)',
-      '_paramsAndChangeChanged(params, _change)',
-      '_patchNumChanged(_patchRange.patchNum)',
-    ],
+    static get observers() {
+      return [
+        '_labelsChanged(_change.labels.*)',
+        '_paramsAndChangeChanged(params, _change)',
+        '_patchNumChanged(_patchRange.patchNum)',
+      ];
+    }
 
     keyboardShortcuts() {
       return {
@@ -325,9 +322,32 @@
         [this.Shortcut.OPEN_DIFF_PREFS]: '_handleOpenDiffPrefsShortcut',
         [this.Shortcut.EDIT_TOPIC]: '_handleEditTopic',
       };
-    },
+    }
+
+    created() {
+      super.created();
+
+      this.addEventListener('topic-changed',
+          () => this._handleTopicChanged());
+
+      this.addEventListener(
+          // When an overlay is opened in a mobile viewport, the overlay has a full
+          // screen view. When it has a full screen view, we do not want the
+          // background to be scrollable. This will eliminate background scroll by
+          // hiding most of the contents on the screen upon opening, and showing
+          // again upon closing.
+          'fullscreen-overlay-opened',
+          () => this._handleHideBackgroundContent());
+
+      this.addEventListener('fullscreen-overlay-closed',
+          () => this._handleShowBackgroundContent());
+
+      this.addEventListener('diff-comments-modified',
+          () => this._handleReloadCommentThreads());
+    }
 
     attached() {
+      super.attached();
       this._getServerConfig().then(config => {
         this._serverConfig = config;
       });
@@ -363,24 +383,25 @@
           this._handleCommitMessageCancel.bind(this));
       this.listen(window, 'scroll', '_handleScroll');
       this.listen(document, 'visibilitychange', '_handleVisibilityChange');
-    },
+    }
 
     detached() {
+      super.detached();
       this.unlisten(window, 'scroll', '_handleScroll');
       this.unlisten(document, 'visibilitychange', '_handleVisibilityChange');
 
       if (this._updateCheckTimerHandle) {
         this._cancelUpdateCheckTimer();
       }
-    },
+    }
 
     get messagesList() {
       return this.$$('gr-messages-list');
-    },
+    }
 
     get threadList() {
       return this.$$('gr-thread-list');
-    },
+    }
 
     /**
      * @param {boolean=} opt_reset
@@ -397,7 +418,7 @@
           this.set('viewState.diffMode', 'SIDE_BY_SIDE');
         }
       });
-    },
+    }
 
     _handleToggleDiffMode(e) {
       if (this.shouldSuppressKeyboardShortcut(e) ||
@@ -409,11 +430,11 @@
       } else {
         this.$.fileListHeader.setDiffViewMode(DiffViewMode.SIDE_BY_SIDE);
       }
-    },
+    }
 
     _handleCommentTabChange() {
       this._showMessagesView = this.$.commentTabs.selected === 0;
-    },
+    }
 
     _handleFileTabChange(e) {
       const selectedIndex = this.$$('#primaryTabs').selected;
@@ -429,7 +450,7 @@
         this.$.reporting.reportInteraction('tab-changed',
             `tabname: ${tabName}, source: ${source}`);
       }
-    },
+    }
 
     _handleShowTab(e) {
       const idx = this._dynamicTabContentEndpoints.indexOf(e.detail.tab);
@@ -440,12 +461,12 @@
       this.$$('#primaryTabs').selected = idx + 1;
       this.$$('#primaryTabs').scrollIntoView();
       this.$.reporting.reportInteraction('show-tab', e.detail.tab);
-    },
+    }
 
     _handleEditCommitMessage(e) {
       this._editingCommitMessage = true;
       this.$.commitMessageEditor.focusTextarea();
-    },
+    }
 
     _handleCommitMessageSave(e) {
       // Trim trailing whitespace from each line.
@@ -466,15 +487,15 @@
       }).catch(err => {
         this.$.commitMessageEditor.disabled = false;
       });
-    },
+    }
 
     _reloadWindow() {
       window.location.reload();
-    },
+    }
 
     _handleCommitMessageCancel(e) {
       this._editingCommitMessage = false;
-    },
+    }
 
     _computeChangeStatusChips(change, mergeable, submitEnabled) {
       // Polymer 2: check for undefined
@@ -498,7 +519,7 @@
         submitEnabled: !!submitEnabled,
       };
       return this.changeStatuses(change, options);
-    },
+    }
 
     _computeHideEditCommitMessage(loggedIn, editing, change, editMode) {
       if (!loggedIn || editing ||
@@ -508,7 +529,7 @@
       }
 
       return false;
-    },
+    }
 
     _handleReloadCommentThreads() {
       // Get any new drafts that have been saved in the diff view and show
@@ -518,7 +539,7 @@
             .map(c => Object.assign({}, c));
         Polymer.dom.flush();
       });
-    },
+    }
 
     _handleReloadDiffComments(e) {
       // Keeps the file list counts updated.
@@ -529,7 +550,7 @@
             e.detail.path);
         Polymer.dom.flush();
       });
-    },
+    }
 
     _computeTotalCommentCounts(unresolvedCount, changeComments) {
       if (!changeComments) return undefined;
@@ -543,7 +564,7 @@
           // Add a comma and space if both unresolved and draft comments exist.
           (unresolvedString && draftString ? ', ' : '') +
           draftString;
-    },
+    }
 
     _handleCommentSave(e) {
       const draft = e.detail.comment;
@@ -575,7 +596,7 @@
         return (c1.line || -1) - (c2.line || -1);
       });
       this._diffDrafts = diffDrafts;
-    },
+    }
 
     _handleCommentDiscard(e) {
       const draft = e.detail.comment;
@@ -609,16 +630,16 @@
         delete diffDrafts[draft.path];
       }
       this._diffDrafts = diffDrafts;
-    },
+    }
 
     _handleReplyTap(e) {
       e.preventDefault();
       this._openReplyDialog(this.$.replyDialog.FocusTarget.ANY);
-    },
+    }
 
     _handleOpenDiffPrefs() {
       this.$.fileList.openDiffPrefs();
-    },
+    }
 
     _handleOpenIncludedInDialog() {
       this.$.includedInDialog.loadData().then(() => {
@@ -626,11 +647,11 @@
         this.$.includedInOverlay.refit();
       });
       this.$.includedInOverlay.open();
-    },
+    }
 
     _handleIncludedInDialogClose(e) {
       this.$.includedInOverlay.close();
-    },
+    }
 
     _handleOpenDownloadDialog() {
       this.$.downloadOverlay.open().then(() => {
@@ -638,19 +659,19 @@
             .setFocusStops(this.$.downloadDialog.getFocusStops());
         this.$.downloadDialog.focus();
       });
-    },
+    }
 
     _handleDownloadDialogClose(e) {
       this.$.downloadOverlay.close();
-    },
+    }
 
     _handleOpenUploadHelpDialog(e) {
       this.$.uploadHelpOverlay.open();
-    },
+    }
 
     _handleCloseUploadHelpDialog(e) {
       this.$.uploadHelpOverlay.close();
-    },
+    }
 
     _handleMessageReply(e) {
       const msg = e.detail.message.message;
@@ -658,33 +679,33 @@
           line => { return '> ' + line; }).join('\n') + '\n\n';
       this.$.replyDialog.quote = quoteStr;
       this._openReplyDialog(this.$.replyDialog.FocusTarget.BODY);
-    },
+    }
 
     _handleHideBackgroundContent() {
       this.$.mainContent.classList.add('overlayOpen');
-    },
+    }
 
     _handleShowBackgroundContent() {
       this.$.mainContent.classList.remove('overlayOpen');
-    },
+    }
 
     _handleReplySent(e) {
       this.$.replyOverlay.close();
       this._reload().then(() => {
         this.$.reporting.timeEnd(SEND_REPLY_TIMING_LABEL);
       });
-    },
+    }
 
     _handleReplyCancel(e) {
       this.$.replyOverlay.close();
-    },
+    }
 
     _handleReplyAutogrow(e) {
       // If the textarea resizes, we need to re-fit the overlay.
       this.debounce('reply-overlay-refit', () => {
         this.$.replyOverlay.refit();
       }, REPLY_REFIT_DEBOUNCE_INTERVAL_MS);
-    },
+    }
 
     _handleShowReplyDialog(e) {
       let target = this.$.replyDialog.FocusTarget.REVIEWERS;
@@ -692,25 +713,25 @@
         target = this.$.replyDialog.FocusTarget.CCS;
       }
       this._openReplyDialog(target);
-    },
+    }
 
     _handleScroll() {
       this.debounce('scroll', () => {
         this.viewState.scrollTop = document.body.scrollTop;
       }, 150);
-    },
+    }
 
     _setShownFiles(e) {
       this._shownFileCount = e.detail.length;
-    },
+    }
 
     _expandAllDiffs() {
       this.$.fileList.expandAllDiffs();
-    },
+    }
 
     _collapseAllDiffs() {
       this.$.fileList.collapseAllDiffs();
-    },
+    }
 
     _paramsChanged(value) {
       // Change the content of the comment tabs back to messages list, but
@@ -764,7 +785,7 @@
       this._reload(true).then(() => {
         this._performPostLoadTasks();
       });
-    },
+    }
 
     _sendShowChangeEvent() {
       this.$.jsAPI.handleEvent(this.$.jsAPI.EventType.SHOW_CHANGE, {
@@ -772,7 +793,7 @@
         patchNum: this._patchRange.patchNum,
         info: {mergeable: this._mergeable},
       });
-    },
+    }
 
     _setPrimaryTab() {
       // Selected has to be set after the paper-tabs are visible because
@@ -780,7 +801,7 @@
       this.$.commentTabs.selected = 0;
       const primaryTabs = this.$$('#primaryTabs');
       if (primaryTabs) primaryTabs.selected = 0;
-    },
+    }
 
     _performPostLoadTasks() {
       this._maybeShowReplyDialog();
@@ -799,7 +820,7 @@
         }
         this._initialLoadComplete = true;
       });
-    },
+    }
 
     _paramsAndChangeChanged(value, change) {
       // Polymer 2: check for undefined
@@ -815,16 +836,16 @@
           patchRangeState.patchNum !== this._patchRange.patchNum) {
         this._resetFileListViewState();
       }
-    },
+    }
 
     _viewStateChanged(viewState) {
       this._numFilesShown = viewState.numFilesShown ?
         viewState.numFilesShown : DEFAULT_NUM_FILES_SHOWN;
-    },
+    }
 
     _numFilesShownChanged(numFilesShown) {
       this.viewState.numFilesShown = numFilesShown;
-    },
+    }
 
     _handleMessageAnchorTap(e) {
       const hash = MSG_PREFIX + e.detail.id;
@@ -832,18 +853,18 @@
           this._patchRange.patchNum, this._patchRange.basePatchNum,
           this._editMode, hash);
       history.replaceState(null, '', url);
-    },
+    }
 
     _maybeScrollToMessage(hash) {
       if (hash.startsWith(MSG_PREFIX)) {
         this.messagesList.scrollToMessage(hash.substr(MSG_PREFIX.length));
       }
-    },
+    }
 
     _getLocationSearch() {
       // Not inlining to make it easier to test.
       return window.location.search;
-    },
+    }
 
     _getUrlParameter(param) {
       const pageURL = this._getLocationSearch().substring(1);
@@ -855,7 +876,7 @@
         }
       }
       return null;
-    },
+    }
 
     _maybeShowRevertDialog() {
       Gerrit.awaitPluginsLoaded()
@@ -871,7 +892,7 @@
               this.$.actions.showRevertDialog();
             }
           });
-    },
+    }
 
     _maybeShowReplyDialog() {
       this._getLoggedIn().then(loggedIn => {
@@ -885,7 +906,7 @@
           this.set('viewState.showReplyDialog', false);
         }
       });
-    },
+    }
 
     _resetFileListViewState() {
       this.set('viewState.selectedFileIndex', 0);
@@ -899,7 +920,7 @@
       }
       this.set('viewState.changeNum', this._changeNum);
       this.set('viewState.patchRange', this._patchRange);
-    },
+    }
 
     _changeChanged(change) {
       if (!change || !this._patchRange || !this._allPatchSets) { return; }
@@ -915,7 +936,7 @@
 
       const title = change.subject + ' (' + change.change_id.substr(0, 9) + ')';
       this.fire('title-change', {title});
-    },
+    }
 
     /**
      * Gets base patch number, if it is a parent try and decide from
@@ -947,19 +968,19 @@
       }
 
       return 'PARENT';
-    },
+    }
 
     _computeShowPrimaryTabs(dynamicTabHeaderEndpoints) {
       return dynamicTabHeaderEndpoints && dynamicTabHeaderEndpoints.length > 0;
-    },
+    }
 
     _computeChangeUrl(change) {
       return Gerrit.Nav.getUrlForChange(change);
-    },
+    }
 
     _computeShowCommitInfo(changeStatus, current_revision) {
       return changeStatus === 'Merged' && current_revision;
-    },
+    }
 
     _computeMergedCommitInfo(current_revision, revisions) {
       const rev = revisions[current_revision];
@@ -968,11 +989,11 @@
       // in <gr-commit-info>. @see Issue 5337
       if (!rev.commit.commit) { rev.commit.commit = current_revision; }
       return rev.commit;
-    },
+    }
 
     _computeChangeIdClass(displayChangeId) {
       return displayChangeId === CHANGE_ID_ERROR.MISMATCH ? 'warning' : '';
-    },
+    }
 
     _computeTitleAttributeWarning(displayChangeId) {
       if (displayChangeId === CHANGE_ID_ERROR.MISMATCH) {
@@ -980,7 +1001,7 @@
       } else if (displayChangeId === CHANGE_ID_ERROR.MISSING) {
         return 'No Change-Id in commit message';
       }
-    },
+    }
 
     _computeChangeIdCommitMessageError(commitMessage, change) {
       // Polymer 2: check for undefined
@@ -1010,11 +1031,11 @@
       }
       // There is no change-id in the commit message.
       return CHANGE_ID_ERROR.MISSING;
-    },
+    }
 
     _computeLabelNames(labels) {
       return Object.keys(labels).sort();
-    },
+    }
 
     _computeLabelValues(labelName, labels) {
       const result = [];
@@ -1039,7 +1060,7 @@
         }
       }
       return result;
-    },
+    }
 
     _computeReplyButtonLabel(changeRecord, canStartReview) {
       // Polymer 2: check for undefined
@@ -1061,7 +1082,7 @@
         label += ' (' + draftCount + ')';
       }
       return label;
-    },
+    }
 
     _handleOpenReplyDialog(e) {
       if (this.shouldSuppressKeyboardShortcut(e) ||
@@ -1077,7 +1098,7 @@
         e.preventDefault();
         this._openReplyDialog(this.$.replyDialog.FocusTarget.ANY);
       });
-    },
+    }
 
     _handleOpenDownloadDialogShortcut(e) {
       if (this.shouldSuppressKeyboardShortcut(e) ||
@@ -1085,7 +1106,7 @@
 
       e.preventDefault();
       this.$.downloadOverlay.open();
-    },
+    }
 
     _handleEditTopic(e) {
       if (this.shouldSuppressKeyboardShortcut(e) ||
@@ -1093,13 +1114,13 @@
 
       e.preventDefault();
       this.$.metadata.editTopic();
-    },
+    }
 
     _handleRefreshChange(e) {
       if (this.shouldSuppressKeyboardShortcut(e)) { return; }
       e.preventDefault();
       Gerrit.Nav.navigateToChange(this._change);
-    },
+    }
 
     _handleToggleChangeStar(e) {
       if (this.shouldSuppressKeyboardShortcut(e) ||
@@ -1107,7 +1128,7 @@
 
       e.preventDefault();
       this.$.changeStar.toggleStar();
-    },
+    }
 
     _handleUpToDashboard(e) {
       if (this.shouldSuppressKeyboardShortcut(e) ||
@@ -1115,7 +1136,7 @@
 
       e.preventDefault();
       this._determinePageBack();
-    },
+    }
 
     _handleExpandAllMessages(e) {
       if (this.shouldSuppressKeyboardShortcut(e) ||
@@ -1123,7 +1144,7 @@
 
       e.preventDefault();
       this.messagesList.handleExpandCollapse(true);
-    },
+    }
 
     _handleCollapseAllMessages(e) {
       if (this.shouldSuppressKeyboardShortcut(e) ||
@@ -1131,7 +1152,7 @@
 
       e.preventDefault();
       this.messagesList.handleExpandCollapse(false);
-    },
+    }
 
     _handleOpenDiffPrefsShortcut(e) {
       if (this.shouldSuppressKeyboardShortcut(e) ||
@@ -1141,14 +1162,14 @@
 
       e.preventDefault();
       this.$.fileList.openDiffPrefs();
-    },
+    }
 
     _determinePageBack() {
       // Default backPage to root if user came to change view page
       // via an email link, etc.
       Gerrit.Nav.navigateToRelativeUrl(this.backPage ||
           Gerrit.Nav.getUrlForRoot());
-    },
+    }
 
     _handleLabelRemoved(splices, path) {
       for (const splice of splices) {
@@ -1163,7 +1184,7 @@
           }
         }
       }
-    },
+    }
 
     _labelsChanged(changeRecord) {
       if (!changeRecord) { return; }
@@ -1174,7 +1195,7 @@
       this.$.jsAPI.handleEvent(this.$.jsAPI.EventType.LABEL_CHANGE, {
         change: this._change,
       });
-    },
+    }
 
     /**
      * @param {string=} opt_section
@@ -1186,7 +1207,7 @@
         Polymer.dom.flush();
         this.$.replyOverlay.center();
       });
-    },
+    }
 
     _handleReloadChange(e) {
       return this._reload().then(() => {
@@ -1197,19 +1218,19 @@
           Gerrit.Nav.navigateToChange(this._change);
         }
       });
-    },
+    }
 
     _handleGetChangeDetailError(response) {
       this.fire('page-error', {response});
-    },
+    }
 
     _getLoggedIn() {
       return this.$.restAPI.getLoggedIn();
-    },
+    }
 
     _getServerConfig() {
       return this.$.restAPI.getConfig();
-    },
+    }
 
     _getProjectConfig() {
       if (!this._change) return;
@@ -1217,18 +1238,18 @@
           config => {
             this._projectConfig = config;
           });
-    },
+    }
 
     _getPreferences() {
       return this.$.restAPI.getPreferences();
-    },
+    }
 
     _prepareCommitMsgForLinkify(msg) {
       // TODO(wyatta) switch linkify sequence, see issue 5526.
       // This is a zero-with space. It is added to prevent the linkify library
       // from including R= or CC= as part of the email address.
       return msg.replace(REVIEWERS_REGEX, '$1=\u200B');
-    },
+    }
 
     /**
      * Utility function to make the necessary modifications to a change in the
@@ -1258,7 +1279,7 @@
         change.revisions[edit.commit.commit].actions =
             change.revisions[edit.base_revision].actions;
       }
-    },
+    }
 
     _getChangeDetail() {
       const detailCompletes = this.$.restAPI.getChangeDetail(
@@ -1312,16 +1333,16 @@
                       parseInt(this._patchRange.patchNum, 10));
             }
           });
-    },
+    }
 
     _isSubmitEnabled(revisionActions) {
       return !!(revisionActions && revisionActions.submit &&
         revisionActions.submit.enabled);
-    },
+    }
 
     _getEdit() {
       return this.$.restAPI.getChangeEdit(this._changeNum, true);
-    },
+    }
 
     _getLatestCommitMessage() {
       return this.$.restAPI.getChangeCommitInfo(this._changeNum,
@@ -1330,7 +1351,7 @@
         this._latestCommitMessage =
                     this._prepareCommitMsgForLinkify(commitInfo.message);
       });
-    },
+    }
 
     _getLatestRevisionSHA(change) {
       if (change.current_revision) {
@@ -1349,7 +1370,7 @@
         }
       }
       return latestRev;
-    },
+    }
 
     _getCommitInfo() {
       return this.$.restAPI.getChangeCommitInfo(
@@ -1357,13 +1378,13 @@
           commitInfo => {
             this._commitInfo = commitInfo;
           });
-    },
+    }
 
     _reloadDraftsWithCallback(e) {
       return this._reloadDrafts().then(() => {
         return e.detail.resolve();
       });
-    },
+    }
 
     /**
      * Fetches a new changeComment object, and data for all types of comments
@@ -1377,7 +1398,7 @@
             this._commentThreads = this._changeComments.getAllThreadsForChange()
                 .map(c => Object.assign({}, c));
           });
-    },
+    }
 
     /**
      * Fetches a new changeComment object, but only updated data for drafts is
@@ -1389,7 +1410,7 @@
             this._changeComments = comments;
             this._diffDrafts = Object.assign({}, this._changeComments.drafts);
           });
-    },
+    }
 
     /**
      * Reload the change.
@@ -1496,7 +1517,7 @@
       });
 
       return coreDataPromise;
-    },
+    }
 
     /**
      * Kicks off requests for resources that rely on the patch range
@@ -1507,7 +1528,7 @@
         this._getCommitInfo(),
         this.$.fileList.reload(),
       ]);
-    },
+    }
 
     _getMergeability() {
       if (!this._change) {
@@ -1527,59 +1548,59 @@
       return this.$.restAPI.getMergeable(this._changeNum).then(m => {
         this._mergeable = m.mergeable;
       });
-    },
+    }
 
     _computeCanStartReview(change) {
       return !!(change.actions && change.actions.ready &&
           change.actions.ready.enabled);
-    },
+    }
 
-    _computeReplyDisabled() { return false; },
+    _computeReplyDisabled() { return false; }
 
     _computeChangePermalinkAriaLabel(changeNum) {
       return 'Change ' + changeNum;
-    },
+    }
 
     _computeCommitClass(collapsed, commitMessage) {
       if (this._computeCommitToggleHidden(commitMessage)) { return ''; }
       return collapsed ? 'collapsed' : '';
-    },
+    }
 
     _computeRelatedChangesClass(collapsed) {
       return collapsed ? 'collapsed' : '';
-    },
+    }
 
     _computeCollapseText(collapsed) {
       // Symbols are up and down triangles.
       return collapsed ? '\u25bc Show more' : '\u25b2 Show less';
-    },
+    }
 
     _toggleCommitCollapsed() {
       this._commitCollapsed = !this._commitCollapsed;
       if (this._commitCollapsed) {
         window.scrollTo(0, 0);
       }
-    },
+    }
 
     _toggleRelatedChangesCollapsed() {
       this._relatedChangesCollapsed = !this._relatedChangesCollapsed;
       if (this._relatedChangesCollapsed) {
         window.scrollTo(0, 0);
       }
-    },
+    }
 
     _computeCommitToggleHidden(commitMessage) {
       if (!commitMessage) { return true; }
       return commitMessage.split('\n').length < MIN_LINES_FOR_COMMIT_COLLAPSE;
-    },
+    }
 
     _getOffsetHeight(element) {
       return element.offsetHeight;
-    },
+    }
 
     _getScrollHeight(element) {
       return element.scrollHeight;
-    },
+    }
 
     /**
      * Get the line height of an element to the nearest integer.
@@ -1587,7 +1608,7 @@
     _getLineHeight(element) {
       const lineHeightStr = getComputedStyle(element).lineHeight;
       return Math.round(lineHeightStr.slice(0, lineHeightStr.length - 2));
-    },
+    }
 
     /**
      * New max height for the related changes section, shorter than the existing
@@ -1646,7 +1667,7 @@
       }
 
       this.updateStyles(stylesToUpdate);
-    },
+    }
 
     _computeShowRelatedToggle() {
       // Make sure the max height has been applied, since there is now content
@@ -1666,7 +1687,7 @@
         return this._showRelatedToggle = true;
       }
       this._showRelatedToggle = false;
-    },
+    }
 
     _updateToggleContainerClass(showRelatedToggle) {
       if (showRelatedToggle) {
@@ -1674,7 +1695,7 @@
       } else {
         this.$.relatedChangesToggle.classList.remove('showToggle');
       }
-    },
+    }
 
     _startUpdateCheckTimer() {
       if (!this._serverConfig ||
@@ -1717,14 +1738,14 @@
           });
         });
       }, this._serverConfig.change.update_delay * 1000);
-    },
+    }
 
     _cancelUpdateCheckTimer() {
       if (this._updateCheckTimerHandle) {
         this.cancelAsync(this._updateCheckTimerHandle);
       }
       this._updateCheckTimerHandle = null;
-    },
+    }
 
     _handleVisibilityChange() {
       if (document.hidden && this._updateCheckTimerHandle) {
@@ -1732,17 +1753,17 @@
       } else if (!this._updateCheckTimerHandle) {
         this._startUpdateCheckTimer();
       }
-    },
+    }
 
     _handleTopicChanged() {
       this.$.relatedChanges.reload();
-    },
+    }
 
     _computeHeaderClass(editMode) {
       const classes = ['header'];
       if (editMode) { classes.push('editMode'); }
       return classes.join(' ');
-    },
+    }
 
     _computeEditMode(patchRangeRecord, paramsRecord) {
       if ([patchRangeRecord, paramsRecord].some(arg => arg === undefined)) {
@@ -1753,7 +1774,7 @@
 
       const patchRange = patchRangeRecord.base || {};
       return this.patchNumEquals(patchRange.patchNum, this.EDIT_NAME);
-    },
+    }
 
     _handleFileActionTap(e) {
       e.preventDefault();
@@ -1775,11 +1796,11 @@
           controls.openRestoreDialog(path);
           break;
       }
-    },
+    }
 
     _computeCommitMessageKey(number, revision) {
       return `c${number}_rev${revision}`;
-    },
+    }
 
     _patchNumChanged(patchNumStr) {
       if (!this._selectedRevision) {
@@ -1791,7 +1812,7 @@
       }
       this._selectedRevision = Object.values(this._change.revisions).find(
           revision => revision._number === patchNum);
-    },
+    }
 
     /**
      * If an edit exists already, load it. Otherwise, toggle edit mode via the
@@ -1814,31 +1835,33 @@
         patchNum = this._patchRange.patchNum;
       }
       Gerrit.Nav.navigateToChange(this._change, patchNum, null, true);
-    },
+    }
 
     _handleStopEditTap() {
       Gerrit.Nav.navigateToChange(this._change, this._patchRange.patchNum);
-    },
+    }
 
     _resetReplyOverlayFocusStops() {
       this.$.replyOverlay.setFocusStops(this.$.replyDialog.getFocusStops());
-    },
+    }
 
     _handleToggleStar(e) {
       this.$.restAPI.saveChangeStarred(e.detail.change._number,
           e.detail.starred);
-    },
+    }
 
     _getRevisionInfo(change) {
       return new Gerrit.RevisionInfo(change);
-    },
+    }
 
     _computeCurrentRevision(currentRevision, revisions) {
       return currentRevision && revisions && revisions[currentRevision];
-    },
+    }
 
     _computeDiffPrefsDisabled(disableDiffPrefs, loggedIn) {
       return disableDiffPrefs || !loggedIn;
-    },
-  });
+    }
+  }
+
+  customElements.define(GrChangeView.is, GrChangeView);
 })();
diff --git a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_test.html b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_test.html
index a697b1a..d9b8464 100644
--- a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_test.html
+++ b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-change-view</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>
diff --git a/polygerrit-ui/app/elements/change/gr-comment-list/gr-comment-list.js b/polygerrit-ui/app/elements/change/gr-comment-list/gr-comment-list.js
index 42cb976..60d459e 100644
--- a/polygerrit-ui/app/elements/change/gr-comment-list/gr-comment-list.js
+++ b/polygerrit-ui/app/elements/change/gr-comment-list/gr-comment-list.js
@@ -16,32 +16,40 @@
  */
 (function() {
   'use strict';
-  Polymer({
-    is: 'gr-comment-list',
 
-    behaviors: [
-      Gerrit.BaseUrlBehavior,
-      Gerrit.PathListBehavior,
-      Gerrit.URLEncodingBehavior,
-    ],
+  /**
+    * @appliesMixin Gerrit.BaseUrlMixin
+    * @appliesMixin Gerrit.PathListMixin
+    * @appliesMixin Gerrit.URLEncodingMixin
+    */
+  class GrCommentList extends Polymer.mixinBehaviors( [
+    Gerrit.BaseUrlBehavior,
+    Gerrit.PathListBehavior,
+    Gerrit.URLEncodingBehavior,
+  ], Polymer.GestureEventListeners(
+      Polymer.LegacyElementMixin(
+          Polymer.Element))) {
+    static get is() { return 'gr-comment-list'; }
 
-    properties: {
-      changeNum: Number,
-      comments: Object,
-      patchNum: Number,
-      projectName: String,
-      /** @type {?} */
-      projectConfig: Object,
-    },
+    static get properties() {
+      return {
+        changeNum: Number,
+        comments: Object,
+        patchNum: Number,
+        projectName: String,
+        /** @type {?} */
+        projectConfig: Object,
+      };
+    }
 
     _computeFilesFromComments(comments) {
       const arr = Object.keys(comments || {});
       return arr.sort(this.specialFilePathCompare);
-    },
+    }
 
     _isOnParent(comment) {
       return comment.side === 'PARENT';
-    },
+    }
 
     _computeDiffLineURL(file, changeNum, patchNum, comment) {
       const basePatchNum = comment.hasOwnProperty('parent') ?
@@ -49,13 +57,13 @@
       return Gerrit.Nav.getUrlForDiffById(this.changeNum, this.projectName,
           file, patchNum, basePatchNum, comment.line,
           this._isOnParent(comment));
-    },
+    }
 
     _computeCommentsForFile(comments, file) {
       // Changes are not picked up by the dom-repeat due to the array instance
       // identity not changing even when it has elements added/removed from it.
       return (comments[file] || []).slice();
-    },
+    }
 
     _computePatchDisplayName(comment) {
       if (this._isOnParent(comment)) {
@@ -65,6 +73,8 @@
         return `PS${comment.patch_set}, `;
       }
       return '';
-    },
-  });
+    }
+  }
+
+  customElements.define(GrCommentList.is, GrCommentList);
 })();
diff --git a/polygerrit-ui/app/elements/change/gr-comment-list/gr-comment-list_test.html b/polygerrit-ui/app/elements/change/gr-comment-list/gr-comment-list_test.html
index c18ae8d..97be9b7 100644
--- a/polygerrit-ui/app/elements/change/gr-comment-list/gr-comment-list_test.html
+++ b/polygerrit-ui/app/elements/change/gr-comment-list/gr-comment-list_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-comment-list</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>
diff --git a/polygerrit-ui/app/elements/change/gr-commit-info/gr-commit-info.js b/polygerrit-ui/app/elements/change/gr-commit-info/gr-commit-info.js
index e2fcdff..2a3fe89 100644
--- a/polygerrit-ui/app/elements/change/gr-commit-info/gr-commit-info.js
+++ b/polygerrit-ui/app/elements/change/gr-commit-info/gr-commit-info.js
@@ -17,23 +17,27 @@
 (function() {
   'use strict';
 
-  Polymer({
-    is: 'gr-commit-info',
+  class GrCommitInfo extends Polymer.GestureEventListeners(
+      Polymer.LegacyElementMixin(
+          Polymer.Element)) {
+    static get is() { return 'gr-commit-info'; }
 
-    properties: {
-      change: Object,
-      /** @type {?} */
-      commitInfo: Object,
-      serverConfig: Object,
-      _showWebLink: {
-        type: Boolean,
-        computed: '_computeShowWebLink(change, commitInfo, serverConfig)',
-      },
-      _webLink: {
-        type: String,
-        computed: '_computeWebLink(change, commitInfo, serverConfig)',
-      },
-    },
+    static get properties() {
+      return {
+        change: Object,
+        /** @type {?} */
+        commitInfo: Object,
+        serverConfig: Object,
+        _showWebLink: {
+          type: Boolean,
+          computed: '_computeShowWebLink(change, commitInfo, serverConfig)',
+        },
+        _webLink: {
+          type: String,
+          computed: '_computeWebLink(change, commitInfo, serverConfig)',
+        },
+      };
+    }
 
     _getWeblink(change, commitInfo, config) {
       return Gerrit.Nav.getPatchSetWeblink(
@@ -43,7 +47,7 @@
             weblinks: commitInfo.web_links,
             config,
           });
-    },
+    }
 
     _computeShowWebLink(change, commitInfo, serverConfig) {
       // Polymer 2: check for undefined
@@ -53,7 +57,7 @@
 
       const weblink = this._getWeblink(change, commitInfo, serverConfig);
       return !!weblink && !!weblink.url;
-    },
+    }
 
     _computeWebLink(change, commitInfo, serverConfig) {
       // Polymer 2: check for undefined
@@ -63,12 +67,14 @@
 
       const {url} = this._getWeblink(change, commitInfo, serverConfig) || {};
       return url;
-    },
+    }
 
     _computeShortHash(commitInfo) {
       const {name} =
             this._getWeblink(this.change, commitInfo, this.serverConfig) || {};
       return name;
-    },
-  });
+    }
+  }
+
+  customElements.define(GrCommitInfo.is, GrCommitInfo);
 })();
diff --git a/polygerrit-ui/app/elements/change/gr-commit-info/gr-commit-info_test.html b/polygerrit-ui/app/elements/change/gr-commit-info/gr-commit-info_test.html
index d3b33d1..2c5b431 100644
--- a/polygerrit-ui/app/elements/change/gr-commit-info/gr-commit-info_test.html
+++ b/polygerrit-ui/app/elements/change/gr-commit-info/gr-commit-info_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-commit-info</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>
diff --git a/polygerrit-ui/app/elements/change/gr-confirm-abandon-dialog/gr-confirm-abandon-dialog.js b/polygerrit-ui/app/elements/change/gr-confirm-abandon-dialog/gr-confirm-abandon-dialog.js
index 524876e..6ef5b69 100644
--- a/polygerrit-ui/app/elements/change/gr-confirm-abandon-dialog/gr-confirm-abandon-dialog.js
+++ b/polygerrit-ui/app/elements/change/gr-confirm-abandon-dialog/gr-confirm-abandon-dialog.js
@@ -17,9 +17,17 @@
 (function() {
   'use strict';
 
-  Polymer({
-    is: 'gr-confirm-abandon-dialog',
-
+  /**
+    * @appliesMixin Gerrit.FireMixin
+    * @appliesMixin Gerrit.KeyboardShortcutMixin
+    */
+  class GrConfirmAbandonDialog extends Polymer.mixinBehaviors( [
+    Gerrit.FireBehavior,
+    Gerrit.KeyboardShortcutBehavior,
+  ], Polymer.GestureEventListeners(
+      Polymer.LegacyElementMixin(
+          Polymer.Element))) {
+    static get is() { return 'gr-confirm-abandon-dialog'; }
     /**
      * Fired when the confirm button is pressed.
      *
@@ -32,41 +40,42 @@
      * @event cancel
      */
 
-    properties: {
-      message: String,
-    },
+    static get properties() {
+      return {
+        message: String,
+      };
+    }
 
-    behaviors: [
-      Gerrit.FireBehavior,
-      Gerrit.KeyboardShortcutBehavior,
-    ],
-
-    keyBindings: {
-      'ctrl+enter meta+enter': '_handleEnterKey',
-    },
+    get keyBindings() {
+      return {
+        'ctrl+enter meta+enter': '_handleEnterKey',
+      };
+    }
 
     resetFocus() {
       this.$.messageInput.textarea.focus();
-    },
+    }
 
     _handleEnterKey(e) {
       this._confirm();
-    },
+    }
 
     _handleConfirmTap(e) {
       e.preventDefault();
       e.stopPropagation();
       this._confirm();
-    },
+    }
 
     _confirm() {
       this.fire('confirm', {reason: this.message}, {bubbles: false});
-    },
+    }
 
     _handleCancelTap(e) {
       e.preventDefault();
       e.stopPropagation();
       this.fire('cancel', null, {bubbles: false});
-    },
-  });
+    }
+  }
+
+  customElements.define(GrConfirmAbandonDialog.is, GrConfirmAbandonDialog);
 })();
diff --git a/polygerrit-ui/app/elements/change/gr-confirm-abandon-dialog/gr-confirm-abandon-dialog_test.html b/polygerrit-ui/app/elements/change/gr-confirm-abandon-dialog/gr-confirm-abandon-dialog_test.html
index cc4b80e..e9964c5 100644
--- a/polygerrit-ui/app/elements/change/gr-confirm-abandon-dialog/gr-confirm-abandon-dialog_test.html
+++ b/polygerrit-ui/app/elements/change/gr-confirm-abandon-dialog/gr-confirm-abandon-dialog_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-confirm-abandon-dialog</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>
diff --git a/polygerrit-ui/app/elements/change/gr-confirm-cherrypick-conflict-dialog/gr-confirm-cherrypick-conflict-dialog.js b/polygerrit-ui/app/elements/change/gr-confirm-cherrypick-conflict-dialog/gr-confirm-cherrypick-conflict-dialog.js
index a0da331..669c6b1 100644
--- a/polygerrit-ui/app/elements/change/gr-confirm-cherrypick-conflict-dialog/gr-confirm-cherrypick-conflict-dialog.js
+++ b/polygerrit-ui/app/elements/change/gr-confirm-cherrypick-conflict-dialog/gr-confirm-cherrypick-conflict-dialog.js
@@ -17,12 +17,15 @@
 (function() {
   'use strict';
 
-  Polymer({
-    is: 'gr-confirm-cherrypick-conflict-dialog',
-
-    behaviors: [
-      Gerrit.FireBehavior,
-    ],
+  /**
+    * @appliesMixin Gerrit.FireMixin
+    */
+  class GrConfirmCherrypickConflictDialog extends Polymer.mixinBehaviors( [
+    Gerrit.FireBehavior,
+  ], Polymer.GestureEventListeners(
+      Polymer.LegacyElementMixin(
+          Polymer.Element))) {
+    static get is() { return 'gr-confirm-cherrypick-conflict-dialog'; }
 
     /**
      * Fired when the confirm button is pressed.
@@ -40,12 +43,15 @@
       e.preventDefault();
       e.stopPropagation();
       this.fire('confirm', null, {bubbles: false});
-    },
+    }
 
     _handleCancelTap(e) {
       e.preventDefault();
       e.stopPropagation();
       this.fire('cancel', null, {bubbles: false});
-    },
-  });
+    }
+  }
+
+  customElements.define(GrConfirmCherrypickConflictDialog.is,
+      GrConfirmCherrypickConflictDialog);
 })();
diff --git a/polygerrit-ui/app/elements/change/gr-confirm-cherrypick-conflict-dialog/gr-confirm-cherrypick-conflict-dialog_test.html b/polygerrit-ui/app/elements/change/gr-confirm-cherrypick-conflict-dialog/gr-confirm-cherrypick-conflict-dialog_test.html
index f411de4..557972a 100644
--- a/polygerrit-ui/app/elements/change/gr-confirm-cherrypick-conflict-dialog/gr-confirm-cherrypick-conflict-dialog_test.html
+++ b/polygerrit-ui/app/elements/change/gr-confirm-cherrypick-conflict-dialog/gr-confirm-cherrypick-conflict-dialog_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-confirm-cherrypick-conflict-dialog</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>
diff --git a/polygerrit-ui/app/elements/change/gr-confirm-cherrypick-dialog/gr-confirm-cherrypick-dialog.js b/polygerrit-ui/app/elements/change/gr-confirm-cherrypick-dialog/gr-confirm-cherrypick-dialog.js
index 5278540..bdc5c10 100644
--- a/polygerrit-ui/app/elements/change/gr-confirm-cherrypick-dialog/gr-confirm-cherrypick-dialog.js
+++ b/polygerrit-ui/app/elements/change/gr-confirm-cherrypick-dialog/gr-confirm-cherrypick-dialog.js
@@ -19,9 +19,15 @@
 
   const SUGGESTIONS_LIMIT = 15;
 
-  Polymer({
-    is: 'gr-confirm-cherrypick-dialog',
-
+  /**
+    * @appliesMixin Gerrit.FireMixin
+    */
+  class GrConfirmCherrypickDialog extends Polymer.mixinBehaviors( [
+    Gerrit.FireBehavior,
+  ], Polymer.GestureEventListeners(
+      Polymer.LegacyElementMixin(
+          Polymer.Element))) {
+    static get is() { return 'gr-confirm-cherrypick-dialog'; }
     /**
      * Fired when the confirm button is pressed.
      *
@@ -34,29 +40,29 @@
      * @event cancel
      */
 
-    properties: {
-      branch: String,
-      baseCommit: String,
-      changeStatus: String,
-      commitMessage: String,
-      commitNum: String,
-      message: String,
-      project: String,
-      _query: {
-        type: Function,
-        value() {
-          return this._getProjectBranchesSuggestions.bind(this);
+    static get properties() {
+      return {
+        branch: String,
+        baseCommit: String,
+        changeStatus: String,
+        commitMessage: String,
+        commitNum: String,
+        message: String,
+        project: String,
+        _query: {
+          type: Function,
+          value() {
+            return this._getProjectBranchesSuggestions.bind(this);
+          },
         },
-      },
-    },
+      };
+    }
 
-    behaviors: [
-      Gerrit.FireBehavior,
-    ],
-
-    observers: [
-      '_computeMessage(changeStatus, commitNum, commitMessage)',
-    ],
+    static get observers() {
+      return [
+        '_computeMessage(changeStatus, commitNum, commitMessage)',
+      ];
+    }
 
     _computeMessage(changeStatus, commitNum, commitMessage) {
       // Polymer 2: check for undefined
@@ -74,23 +80,23 @@
         newMessage += '(cherry picked from commit ' + commitNum + ')';
       }
       this.message = newMessage;
-    },
+    }
 
     _handleConfirmTap(e) {
       e.preventDefault();
       e.stopPropagation();
       this.fire('confirm', null, {bubbles: false});
-    },
+    }
 
     _handleCancelTap(e) {
       e.preventDefault();
       e.stopPropagation();
       this.fire('cancel', null, {bubbles: false});
-    },
+    }
 
     resetFocus() {
       this.$.branchInput.focus();
-    },
+    }
 
     _getProjectBranchesSuggestions(input) {
       if (input.startsWith('refs/heads/')) {
@@ -113,6 +119,9 @@
         }
         return branches;
       });
-    },
-  });
+    }
+  }
+
+  customElements.define(GrConfirmCherrypickDialog.is,
+      GrConfirmCherrypickDialog);
 })();
diff --git a/polygerrit-ui/app/elements/change/gr-confirm-cherrypick-dialog/gr-confirm-cherrypick-dialog_test.html b/polygerrit-ui/app/elements/change/gr-confirm-cherrypick-dialog/gr-confirm-cherrypick-dialog_test.html
index 22a2aba..42310fb 100644
--- a/polygerrit-ui/app/elements/change/gr-confirm-cherrypick-dialog/gr-confirm-cherrypick-dialog_test.html
+++ b/polygerrit-ui/app/elements/change/gr-confirm-cherrypick-dialog/gr-confirm-cherrypick-dialog_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-confirm-cherrypick-dialog</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>
diff --git a/polygerrit-ui/app/elements/change/gr-confirm-move-dialog/gr-confirm-move-dialog.js b/polygerrit-ui/app/elements/change/gr-confirm-move-dialog/gr-confirm-move-dialog.js
index c6b0adf..fb78bfb 100644
--- a/polygerrit-ui/app/elements/change/gr-confirm-move-dialog/gr-confirm-move-dialog.js
+++ b/polygerrit-ui/app/elements/change/gr-confirm-move-dialog/gr-confirm-move-dialog.js
@@ -19,9 +19,15 @@
 
   const SUGGESTIONS_LIMIT = 15;
 
-  Polymer({
-    is: 'gr-confirm-move-dialog',
-
+  /**
+    * @appliesMixin Gerrit.FireMixin
+    */
+  class GrConfirmMoveDialog extends Polymer.mixinBehaviors( [
+    Gerrit.FireBehavior,
+  ], Polymer.GestureEventListeners(
+      Polymer.LegacyElementMixin(
+          Polymer.Element))) {
+    static get is() { return 'gr-confirm-move-dialog'; }
     /**
      * Fired when the confirm button is pressed.
      *
@@ -34,33 +40,31 @@
      * @event cancel
      */
 
-    properties: {
-      branch: String,
-      message: String,
-      project: String,
-      _query: {
-        type: Function,
-        value() {
-          return this._getProjectBranchesSuggestions.bind(this);
+    static get properties() {
+      return {
+        branch: String,
+        message: String,
+        project: String,
+        _query: {
+          type: Function,
+          value() {
+            return this._getProjectBranchesSuggestions.bind(this);
+          },
         },
-      },
-    },
-
-    behaviors: [
-      Gerrit.FireBehavior,
-    ],
+      };
+    }
 
     _handleConfirmTap(e) {
       e.preventDefault();
       e.stopPropagation();
       this.fire('confirm', null, {bubbles: false});
-    },
+    }
 
     _handleCancelTap(e) {
       e.preventDefault();
       e.stopPropagation();
       this.fire('cancel', null, {bubbles: false});
-    },
+    }
 
     _getProjectBranchesSuggestions(input) {
       if (input.startsWith('refs/heads/')) {
@@ -83,6 +87,8 @@
         }
         return branches;
       });
-    },
-  });
+    }
+  }
+
+  customElements.define(GrConfirmMoveDialog.is, GrConfirmMoveDialog);
 })();
diff --git a/polygerrit-ui/app/elements/change/gr-confirm-move-dialog/gr-confirm-move-dialog_test.html b/polygerrit-ui/app/elements/change/gr-confirm-move-dialog/gr-confirm-move-dialog_test.html
index 8d6e029..036950b 100644
--- a/polygerrit-ui/app/elements/change/gr-confirm-move-dialog/gr-confirm-move-dialog_test.html
+++ b/polygerrit-ui/app/elements/change/gr-confirm-move-dialog/gr-confirm-move-dialog_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-confirm-move-dialog</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>
diff --git a/polygerrit-ui/app/elements/change/gr-confirm-rebase-dialog/gr-confirm-rebase-dialog.js b/polygerrit-ui/app/elements/change/gr-confirm-rebase-dialog/gr-confirm-rebase-dialog.js
index 54ce271..7169437 100644
--- a/polygerrit-ui/app/elements/change/gr-confirm-rebase-dialog/gr-confirm-rebase-dialog.js
+++ b/polygerrit-ui/app/elements/change/gr-confirm-rebase-dialog/gr-confirm-rebase-dialog.js
@@ -17,9 +17,10 @@
 (function() {
   'use strict';
 
-  Polymer({
-    is: 'gr-confirm-rebase-dialog',
-
+  class GrConfirmRebaseDialog extends Polymer.GestureEventListeners(
+      Polymer.LegacyElementMixin(
+          Polymer.Element)) {
+    static get is() { return 'gr-confirm-rebase-dialog'; }
     /**
      * Fired when the confirm button is pressed.
      *
@@ -32,24 +33,28 @@
      * @event cancel
      */
 
-    properties: {
-      branch: String,
-      changeNumber: Number,
-      hasParent: Boolean,
-      rebaseOnCurrent: Boolean,
-      _text: String,
-      _query: {
-        type: Function,
-        value() {
-          return this._getChangeSuggestions.bind(this);
+    static get properties() {
+      return {
+        branch: String,
+        changeNumber: Number,
+        hasParent: Boolean,
+        rebaseOnCurrent: Boolean,
+        _text: String,
+        _query: {
+          type: Function,
+          value() {
+            return this._getChangeSuggestions.bind(this);
+          },
         },
-      },
-      _recentChanges: Array,
-    },
+        _recentChanges: Array,
+      };
+    }
 
-    observers: [
-      '_updateSelectedOption(rebaseOnCurrent, hasParent)',
-    ],
+    static get observers() {
+      return [
+        '_updateSelectedOption(rebaseOnCurrent, hasParent)',
+      ];
+    }
 
     // This is called by gr-change-actions every time the rebase dialog is
     // re-opened. Unlike other autocompletes that make a request with each
@@ -71,36 +76,36 @@
             this._recentChanges = changes;
             return this._recentChanges;
           });
-    },
+    }
 
     _getRecentChanges() {
       if (this._recentChanges) {
         return Promise.resolve(this._recentChanges);
       }
       return this.fetchRecentChanges();
-    },
+    }
 
     _getChangeSuggestions(input) {
       return this._getRecentChanges().then(changes =>
         this._filterChanges(input, changes));
-    },
+    }
 
     _filterChanges(input, changes) {
       return changes.filter(change => change.name.includes(input) &&
           change.value !== this.changeNumber);
-    },
+    }
 
     _displayParentOption(rebaseOnCurrent, hasParent) {
       return hasParent && rebaseOnCurrent;
-    },
+    }
 
     _displayParentUpToDateMsg(rebaseOnCurrent, hasParent) {
       return hasParent && !rebaseOnCurrent;
-    },
+    }
 
     _displayTipOption(rebaseOnCurrent, hasParent) {
       return !(!rebaseOnCurrent && !hasParent);
-    },
+    }
 
     /**
      * There is a subtle but important difference between setting the base to an
@@ -115,7 +120,7 @@
       // Change numbers will have their description appended by the
       // autocomplete.
       return this._text.split(':')[0];
-    },
+    }
 
     _handleConfirmTap(e) {
       e.preventDefault();
@@ -123,22 +128,22 @@
       this.dispatchEvent(new CustomEvent('confirm',
           {detail: {base: this._getSelectedBase()}}));
       this._text = '';
-    },
+    }
 
     _handleCancelTap(e) {
       e.preventDefault();
       e.stopPropagation();
       this.dispatchEvent(new CustomEvent('cancel'));
       this._text = '';
-    },
+    }
 
     _handleRebaseOnOther() {
       this.$.parentInput.focus();
-    },
+    }
 
     _handleEnterChangeNumberClick() {
       this.$.rebaseOnOtherInput.checked = true;
-    },
+    }
 
     /**
      * Sets the default radio button based on the state of the app and
@@ -157,6 +162,8 @@
       } else {
         this.$.rebaseOnOtherInput.checked = true;
       }
-    },
-  });
+    }
+  }
+
+  customElements.define(GrConfirmRebaseDialog.is, GrConfirmRebaseDialog);
 })();
diff --git a/polygerrit-ui/app/elements/change/gr-confirm-rebase-dialog/gr-confirm-rebase-dialog_test.html b/polygerrit-ui/app/elements/change/gr-confirm-rebase-dialog/gr-confirm-rebase-dialog_test.html
index cd5b130..b116171 100644
--- a/polygerrit-ui/app/elements/change/gr-confirm-rebase-dialog/gr-confirm-rebase-dialog_test.html
+++ b/polygerrit-ui/app/elements/change/gr-confirm-rebase-dialog/gr-confirm-rebase-dialog_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-confirm-rebase-dialog</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>
diff --git a/polygerrit-ui/app/elements/change/gr-confirm-revert-dialog/gr-confirm-revert-dialog.js b/polygerrit-ui/app/elements/change/gr-confirm-revert-dialog/gr-confirm-revert-dialog.js
index 662b64c..e95f2c9 100644
--- a/polygerrit-ui/app/elements/change/gr-confirm-revert-dialog/gr-confirm-revert-dialog.js
+++ b/polygerrit-ui/app/elements/change/gr-confirm-revert-dialog/gr-confirm-revert-dialog.js
@@ -20,9 +20,15 @@
   const ERR_COMMIT_NOT_FOUND =
       'Unable to find the commit hash of this change.';
 
-  Polymer({
-    is: 'gr-confirm-revert-dialog',
-
+  /**
+    * @appliesMixin Gerrit.FireMixin
+    */
+  class GrConfirmRevertDialog extends Polymer.mixinBehaviors( [
+    Gerrit.FireBehavior,
+  ], Polymer.GestureEventListeners(
+      Polymer.LegacyElementMixin(
+          Polymer.Element))) {
+    static get is() { return 'gr-confirm-revert-dialog'; }
     /**
      * Fired when the confirm button is pressed.
      *
@@ -35,13 +41,11 @@
      * @event cancel
      */
 
-    properties: {
-      message: String,
-    },
-
-    behaviors: [
-      Gerrit.FireBehavior,
-    ],
+    static get properties() {
+      return {
+        message: String,
+      };
+    }
 
     populateRevertMessage(message, commitHash) {
       // Figure out what the revert title should be.
@@ -55,18 +59,20 @@
 
       this.message = `${revertTitle}\n\n${revertCommitText}\n\n` +
           `Reason for revert: <INSERT REASONING HERE>\n`;
-    },
+    }
 
     _handleConfirmTap(e) {
       e.preventDefault();
       e.stopPropagation();
       this.fire('confirm', null, {bubbles: false});
-    },
+    }
 
     _handleCancelTap(e) {
       e.preventDefault();
       e.stopPropagation();
       this.fire('cancel', null, {bubbles: false});
-    },
-  });
+    }
+  }
+
+  customElements.define(GrConfirmRevertDialog.is, GrConfirmRevertDialog);
 })();
diff --git a/polygerrit-ui/app/elements/change/gr-confirm-revert-dialog/gr-confirm-revert-dialog_test.html b/polygerrit-ui/app/elements/change/gr-confirm-revert-dialog/gr-confirm-revert-dialog_test.html
index 6e41555..dbdfba2 100644
--- a/polygerrit-ui/app/elements/change/gr-confirm-revert-dialog/gr-confirm-revert-dialog_test.html
+++ b/polygerrit-ui/app/elements/change/gr-confirm-revert-dialog/gr-confirm-revert-dialog_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-confirm-revert-dialog</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>
diff --git a/polygerrit-ui/app/elements/change/gr-confirm-revert-submission-dialog/gr-confirm-revert-submission-dialog.js b/polygerrit-ui/app/elements/change/gr-confirm-revert-submission-dialog/gr-confirm-revert-submission-dialog.js
index 6cbbb37..04b16ee 100644
--- a/polygerrit-ui/app/elements/change/gr-confirm-revert-submission-dialog/gr-confirm-revert-submission-dialog.js
+++ b/polygerrit-ui/app/elements/change/gr-confirm-revert-submission-dialog/gr-confirm-revert-submission-dialog.js
@@ -20,9 +20,15 @@
   const ERR_COMMIT_NOT_FOUND =
       'Unable to find the commit hash of this change.';
 
-  Polymer({
-    is: 'gr-confirm-revert-submission-dialog',
-
+  /**
+    * @appliesMixin Gerrit.FireMixin
+    */
+  class GrConfirmRevertSubmissionDialog extends Polymer.mixinBehaviors( [
+    Gerrit.FireBehavior,
+  ], Polymer.GestureEventListeners(
+      Polymer.LegacyElementMixin(
+          Polymer.Element))) {
+    static get is() { return 'gr-confirm-revert-submission-dialog'; }
     /**
      * Fired when the confirm button is pressed.
      *
@@ -35,13 +41,11 @@
      * @event cancel
      */
 
-    properties: {
-      message: String,
-    },
-
-    behaviors: [
-      Gerrit.FireBehavior,
-    ],
+    static get properties() {
+      return {
+        message: String,
+      };
+    }
 
     populateRevertSubmissionMessage(message, commitHash) {
       // Follow the same convention of the revert
@@ -52,18 +56,21 @@
       }
       this.message = `${revertTitle}\n\n` +
           `Reason for revert: <INSERT REASONING HERE>\n`;
-    },
+    }
 
     _handleConfirmTap(e) {
       e.preventDefault();
       e.stopPropagation();
       this.fire('confirm', null, {bubbles: false});
-    },
+    }
 
     _handleCancelTap(e) {
       e.preventDefault();
       e.stopPropagation();
       this.fire('cancel', null, {bubbles: false});
-    },
-  });
+    }
+  }
+
+  customElements.define(GrConfirmRevertSubmissionDialog.is,
+      GrConfirmRevertSubmissionDialog);
 })();
diff --git a/polygerrit-ui/app/elements/change/gr-confirm-revert-submission-dialog/gr-confirm-revert-submission-dialog_test.html b/polygerrit-ui/app/elements/change/gr-confirm-revert-submission-dialog/gr-confirm-revert-submission-dialog_test.html
index c0f2dc3..cc4bd54 100644
--- a/polygerrit-ui/app/elements/change/gr-confirm-revert-submission-dialog/gr-confirm-revert-submission-dialog_test.html
+++ b/polygerrit-ui/app/elements/change/gr-confirm-revert-submission-dialog/gr-confirm-revert-submission-dialog_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-confirm-revert-submission-dialog</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>
diff --git a/polygerrit-ui/app/elements/change/gr-confirm-submit-dialog/gr-confirm-submit-dialog.js b/polygerrit-ui/app/elements/change/gr-confirm-submit-dialog/gr-confirm-submit-dialog.js
index e86e21c..05841e9 100644
--- a/polygerrit-ui/app/elements/change/gr-confirm-submit-dialog/gr-confirm-submit-dialog.js
+++ b/polygerrit-ui/app/elements/change/gr-confirm-submit-dialog/gr-confirm-submit-dialog.js
@@ -17,9 +17,10 @@
 (function() {
   'use strict';
 
-  Polymer({
-    is: 'gr-confirm-submit-dialog',
-
+  class GrConfirmSubmitDialog extends Polymer.GestureEventListeners(
+      Polymer.LegacyElementMixin(
+          Polymer.Element)) {
+    static get is() { return 'gr-confirm-submit-dialog'; }
     /**
      * Fired when the confirm button is pressed.
      *
@@ -32,37 +33,41 @@
      * @event cancel
      */
 
-    properties: {
+    static get properties() {
+      return {
       /**
        * @type {{
        *    is_private: boolean,
        *    subject: string,
        *  }}
        */
-      change: Object,
+        change: Object,
 
-      /**
+        /**
        * @type {{
        *    label: string,
        *  }}
        */
-      action: Object,
-    },
+        action: Object,
+      };
+    }
 
     resetFocus(e) {
       this.$.dialog.resetFocus();
-    },
+    }
 
     _handleConfirmTap(e) {
       e.preventDefault();
       e.stopPropagation();
       this.dispatchEvent(new CustomEvent('confirm', {bubbles: false}));
-    },
+    }
 
     _handleCancelTap(e) {
       e.preventDefault();
       e.stopPropagation();
       this.dispatchEvent(new CustomEvent('cancel', {bubbles: false}));
-    },
-  });
+    }
+  }
+
+  customElements.define(GrConfirmSubmitDialog.is, GrConfirmSubmitDialog);
 })();
diff --git a/polygerrit-ui/app/elements/change/gr-confirm-submit-dialog/gr-confirm-submit-dialog_test.html b/polygerrit-ui/app/elements/change/gr-confirm-submit-dialog/gr-confirm-submit-dialog_test.html
index 40fa29a..515147f 100644
--- a/polygerrit-ui/app/elements/change/gr-confirm-submit-dialog/gr-confirm-submit-dialog_test.html
+++ b/polygerrit-ui/app/elements/change/gr-confirm-submit-dialog/gr-confirm-submit-dialog_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-confirm-submit-dialog</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>
diff --git a/polygerrit-ui/app/elements/change/gr-download-dialog/gr-download-dialog.js b/polygerrit-ui/app/elements/change/gr-download-dialog/gr-download-dialog.js
index b2e5e63..ab849de 100644
--- a/polygerrit-ui/app/elements/change/gr-download-dialog/gr-download-dialog.js
+++ b/polygerrit-ui/app/elements/change/gr-download-dialog/gr-download-dialog.js
@@ -17,40 +17,47 @@
 (function() {
   'use strict';
 
-  Polymer({
-    is: 'gr-download-dialog',
-
+  /**
+    * @appliesMixin Gerrit.FireMixin
+    * @appliesMixin Gerrit.PatchSetMixin
+    * @appliesMixin Gerrit.RESTClientMixin
+    */
+  class GrDownloadDialog extends Polymer.mixinBehaviors( [
+    Gerrit.FireBehavior,
+    Gerrit.PatchSetBehavior,
+    Gerrit.RESTClientBehavior,
+  ], Polymer.GestureEventListeners(
+      Polymer.LegacyElementMixin(
+          Polymer.Element))) {
+    static get is() { return 'gr-download-dialog'; }
     /**
      * Fired when the user presses the close button.
      *
      * @event close
      */
 
-    properties: {
+    static get properties() {
+      return {
       /** @type {{ revisions: Array }} */
-      change: Object,
-      patchNum: String,
-      /** @type {?} */
-      config: Object,
+        change: Object,
+        patchNum: String,
+        /** @type {?} */
+        config: Object,
 
-      _schemes: {
-        type: Array,
-        value() { return []; },
-        computed: '_computeSchemes(change, patchNum)',
-        observer: '_schemesChanged',
-      },
-      _selectedScheme: String,
-    },
+        _schemes: {
+          type: Array,
+          value() { return []; },
+          computed: '_computeSchemes(change, patchNum)',
+          observer: '_schemesChanged',
+        },
+        _selectedScheme: String,
+      };
+    }
 
-    hostAttributes: {
-      role: 'dialog',
-    },
-
-    behaviors: [
-      Gerrit.FireBehavior,
-      Gerrit.PatchSetBehavior,
-      Gerrit.RESTClientBehavior,
-    ],
+    ready() {
+      super.ready();
+      this._ensureAttribute('role', 'dialog');
+    }
 
     focus() {
       if (this._schemes.length) {
@@ -58,7 +65,7 @@
       } else {
         this.$.download.focus();
       }
-    },
+    }
 
     getFocusStops() {
       const links = this.$$('#archives').querySelectorAll('a');
@@ -66,7 +73,7 @@
         start: this.$.closeButton,
         end: links[links.length - 1],
       };
-    },
+    }
 
     _computeDownloadCommands(change, patchNum, _selectedScheme) {
       let commandObj;
@@ -87,7 +94,7 @@
         });
       }
       return commands;
-    },
+    }
 
     /**
      * @param {!Object} change
@@ -97,7 +104,7 @@
      */
     _computeZipDownloadLink(change, patchNum) {
       return this._computeDownloadLink(change, patchNum, true);
-    },
+    }
 
     /**
      * @param {!Object} change
@@ -107,7 +114,7 @@
      */
     _computeZipDownloadFilename(change, patchNum) {
       return this._computeDownloadFilename(change, patchNum, true);
-    },
+    }
 
     /**
      * @param {!Object} change
@@ -123,7 +130,7 @@
       }
       return this.changeBaseURL(change.project, change._number, patchNum) +
           '/patch?' + (opt_zip ? 'zip' : 'download');
-    },
+    }
 
     /**
      * @param {!Object} change
@@ -146,7 +153,7 @@
         }
       }
       return shortRev + '.diff.' + (opt_zip ? 'zip' : 'base64');
-    },
+    }
 
     _computeHidePatchFile(change, patchNum) {
       // Polymer 2: check for undefined
@@ -161,7 +168,7 @@
         }
       }
       return false;
-    },
+    }
 
     _computeArchiveDownloadLink(change, patchNum, format) {
       // Polymer 2: check for undefined
@@ -170,7 +177,7 @@
       }
       return this.changeBaseURL(change.project, change._number, patchNum) +
           '/archive?format=' + format;
-    },
+    }
 
     _computeSchemes(change, patchNum) {
       // Polymer 2: check for undefined
@@ -188,28 +195,30 @@
         }
       }
       return [];
-    },
+    }
 
     _computePatchSetQuantity(revisions) {
       if (!revisions) { return 0; }
       return Object.keys(revisions).length;
-    },
+    }
 
     _handleCloseTap(e) {
       e.preventDefault();
       e.stopPropagation();
       this.fire('close', null, {bubbles: false});
-    },
+    }
 
     _schemesChanged(schemes) {
       if (schemes.length === 0) { return; }
       if (!schemes.includes(this._selectedScheme)) {
         this._selectedScheme = schemes.sort()[0];
       }
-    },
+    }
 
     _computeShowDownloadCommands(schemes) {
       return schemes.length ? '' : 'hidden';
-    },
-  });
+    }
+  }
+
+  customElements.define(GrDownloadDialog.is, GrDownloadDialog);
 })();
diff --git a/polygerrit-ui/app/elements/change/gr-download-dialog/gr-download-dialog_test.html b/polygerrit-ui/app/elements/change/gr-download-dialog/gr-download-dialog_test.html
index 82574808..0f1260f 100644
--- a/polygerrit-ui/app/elements/change/gr-download-dialog/gr-download-dialog_test.html
+++ b/polygerrit-ui/app/elements/change/gr-download-dialog/gr-download-dialog_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-download-dialog</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>
diff --git a/polygerrit-ui/app/elements/change/gr-file-list-header/gr-file-list-header.js b/polygerrit-ui/app/elements/change/gr-file-list-header/gr-file-list-header.js
index b8095bf..dded147 100644
--- a/polygerrit-ui/app/elements/change/gr-file-list-header/gr-file-list-header.js
+++ b/polygerrit-ui/app/elements/change/gr-file-list-header/gr-file-list-header.js
@@ -21,9 +21,17 @@
   const PATCH_DESC_MAX_LENGTH = 500;
   const MERGED_STATUS = 'MERGED';
 
-  Polymer({
-    is: 'gr-file-list-header',
-
+  /**
+    * @appliesMixin Gerrit.FireMixin
+    * @appliesMixin Gerrit.PatchSetMixin
+    */
+  class GrFileListHeader extends Polymer.mixinBehaviors( [
+    Gerrit.FireBehavior,
+    Gerrit.PatchSetBehavior,
+  ], Polymer.GestureEventListeners(
+      Polymer.LegacyElementMixin(
+          Polymer.Element))) {
+    static get is() { return 'gr-file-list-header'; }
     /**
      * @event expand-diffs
      */
@@ -48,72 +56,71 @@
      * @event open-upload-help-dialog
      */
 
-    properties: {
-      account: Object,
-      allPatchSets: Array,
-      /** @type {?} */
-      change: Object,
-      changeNum: String,
-      changeUrl: String,
-      changeComments: Object,
-      commitInfo: Object,
-      editMode: Boolean,
-      loggedIn: Boolean,
-      serverConfig: Object,
-      shownFileCount: Number,
-      diffPrefs: Object,
-      diffPrefsDisabled: Boolean,
-      diffViewMode: {
-        type: String,
-        notify: true,
-      },
-      patchNum: String,
-      basePatchNum: String,
-      filesExpanded: String,
-      // Caps the number of files that can be shown and have the 'show diffs' /
-      // 'hide diffs' buttons still be functional.
-      _maxFilesForBulkActions: {
-        type: Number,
-        readOnly: true,
-        value: 225,
-      },
-      _patchsetDescription: {
-        type: String,
-        value: '',
-      },
-      showTitle: {
-        type: Boolean,
-        value: true,
-      },
-      _descriptionReadOnly: {
-        type: Boolean,
-        computed: '_computeDescriptionReadOnly(loggedIn, change, account)',
-      },
-      revisionInfo: Object,
-    },
+    static get properties() {
+      return {
+        account: Object,
+        allPatchSets: Array,
+        /** @type {?} */
+        change: Object,
+        changeNum: String,
+        changeUrl: String,
+        changeComments: Object,
+        commitInfo: Object,
+        editMode: Boolean,
+        loggedIn: Boolean,
+        serverConfig: Object,
+        shownFileCount: Number,
+        diffPrefs: Object,
+        diffPrefsDisabled: Boolean,
+        diffViewMode: {
+          type: String,
+          notify: true,
+        },
+        patchNum: String,
+        basePatchNum: String,
+        filesExpanded: String,
+        // Caps the number of files that can be shown and have the 'show diffs' /
+        // 'hide diffs' buttons still be functional.
+        _maxFilesForBulkActions: {
+          type: Number,
+          readOnly: true,
+          value: 225,
+        },
+        _patchsetDescription: {
+          type: String,
+          value: '',
+        },
+        showTitle: {
+          type: Boolean,
+          value: true,
+        },
+        _descriptionReadOnly: {
+          type: Boolean,
+          computed: '_computeDescriptionReadOnly(loggedIn, change, account)',
+        },
+        revisionInfo: Object,
+      };
+    }
 
-    behaviors: [
-      Gerrit.FireBehavior,
-      Gerrit.PatchSetBehavior,
-    ],
-
-    observers: [
-      '_computePatchSetDescription(change, patchNum)',
-    ],
+    static get observers() {
+      return [
+        '_computePatchSetDescription(change, patchNum)',
+      ];
+    }
 
     setDiffViewMode(mode) {
       this.$.modeSelect.setMode(mode);
-    },
+    }
 
     _expandAllDiffs() {
       this._expanded = true;
       this.fire('expand-diffs');
-    },
+    }
 
     _collapseAllDiffs() {
       this._expanded = false;
       this.fire('collapse-diffs');
-    },
+    }
 
     _computeExpandedClass(filesExpanded) {
       const classes = [];
@@ -125,11 +132,11 @@
         classes.push('openFile');
       }
       return classes.join(' ');
-    },
+    }
 
     _computeDescriptionPlaceholder(readOnly) {
       return (readOnly ? 'No' : 'Add') + ' patchset description';
-    },
+    }
 
     _computeDescriptionReadOnly(loggedIn, change, account) {
       // Polymer 2: check for undefined
@@ -138,7 +145,7 @@
       }
 
       return !(loggedIn && (account._account_id === change.owner._account_id));
-    },
+    }
 
     _computePatchSetDescription(change, patchNum) {
       // Polymer 2: check for undefined
@@ -149,11 +156,11 @@
       const rev = this.getRevisionByPatchNum(change.revisions, patchNum);
       this._patchsetDescription = (rev && rev.description) ?
         rev.description.substring(0, PATCH_DESC_MAX_LENGTH) : '';
-    },
+    }
 
     _handleDescriptionRemoved(e) {
       return this._updateDescription('', e);
-    },
+    }
 
     /**
      * @param {!Object} revisions The revisions object keyed by revision hashes
@@ -167,12 +174,12 @@
           return rev;
         }
       }
-    },
+    }
 
     _handleDescriptionChanged(e) {
       const desc = e.detail.trim();
       this._updateDescription(desc, e);
-    },
+    }
 
     /**
      * Update the patchset description with the rest API.
@@ -197,43 +204,43 @@
             if (target) { target.disabled = false; }
             return;
           });
-    },
+    }
 
     _computePrefsButtonHidden(prefs, diffPrefsDisabled) {
       return diffPrefsDisabled || !prefs;
-    },
+    }
 
     _fileListActionsVisible(shownFileCount, maxFilesForBulkActions) {
       return shownFileCount <= maxFilesForBulkActions;
-    },
+    }
 
     _handlePatchChange(e) {
       const {basePatchNum, patchNum} = e.detail;
       if (this.patchNumEquals(basePatchNum, this.basePatchNum) &&
           this.patchNumEquals(patchNum, this.patchNum)) { return; }
       Gerrit.Nav.navigateToChange(this.change, patchNum, basePatchNum);
-    },
+    }
 
     _handlePrefsTap(e) {
       e.preventDefault();
       this.fire('open-diff-prefs');
-    },
+    }
 
     _handleIncludedInTap(e) {
       e.preventDefault();
       this.fire('open-included-in-dialog');
-    },
+    }
 
     _handleDownloadTap(e) {
       e.preventDefault();
       e.stopPropagation();
       this.dispatchEvent(
           new CustomEvent('open-download-dialog', {bubbles: false}));
-    },
+    }
 
     _computeEditModeClass(editMode) {
       return editMode ? 'editMode' : '';
-    },
+    }
 
     _computePatchInfoClass(patchNum, allPatchSets) {
       const latestNum = this.computeLatestPatchNum(allPatchSets);
@@ -241,18 +248,18 @@
         return '';
       }
       return 'patchInfoOldPatchSet';
-    },
+    }
 
     _hideIncludedIn(change) {
       return change && change.status === MERGED_STATUS ? '' : 'hide';
-    },
+    }
 
     _handleUploadTap(e) {
       e.preventDefault();
       e.stopPropagation();
       this.dispatchEvent(
           new CustomEvent('open-upload-help-dialog', {bubbles: false}));
-    },
+    }
 
     _computeUploadHelpContainerClass(change, account) {
       const changeIsMerged = change && change.status === MERGED_STATUS;
@@ -262,6 +269,8 @@
       const userIsOwner = ownerId && userId && ownerId === userId;
       const hideContainer = !userIsOwner || changeIsMerged;
       return 'uploadContainer desktop' + (hideContainer ? ' hide' : '');
-    },
-  });
+    }
+  }
+
+  customElements.define(GrFileListHeader.is, GrFileListHeader);
 })();
diff --git a/polygerrit-ui/app/elements/change/gr-file-list-header/gr-file-list-header_test.html b/polygerrit-ui/app/elements/change/gr-file-list-header/gr-file-list-header_test.html
index ac626ab..ea7ea8f 100644
--- a/polygerrit-ui/app/elements/change/gr-file-list-header/gr-file-list-header_test.html
+++ b/polygerrit-ui/app/elements/change/gr-file-list-header/gr-file-list-header_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-file-list-header</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>
diff --git a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.js b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.js
index 6d1e376..fb57164 100644
--- a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.js
+++ b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.js
@@ -41,162 +41,175 @@
     U: 'Unchanged',
   };
 
-  Polymer({
-    is: 'gr-file-list',
-
+  /**
+    * @appliesMixin Gerrit.AsyncForeachMixin
+    * @appliesMixin Gerrit.DomUtilMixin
+    * @appliesMixin Gerrit.FireMixin
+    * @appliesMixin Gerrit.KeyboardShortcutMixin
+    * @appliesMixin Gerrit.PatchSetMixin
+    * @appliesMixin Gerrit.PathListMixin
+    */
+  class GrFileList extends Polymer.mixinBehaviors( [
+    Gerrit.AsyncForeachBehavior,
+    Gerrit.DomUtilBehavior,
+    Gerrit.FireBehavior,
+    Gerrit.KeyboardShortcutBehavior,
+    Gerrit.PatchSetBehavior,
+    Gerrit.PathListBehavior,
+  ], Polymer.GestureEventListeners(
+      Polymer.LegacyElementMixin(
+          Polymer.Element))) {
+    static get is() { return 'gr-file-list'; }
     /**
      * Fired when a draft refresh should get triggered
      *
      * @event reload-drafts
      */
 
-    properties: {
+    static get properties() {
+      return {
       /** @type {?} */
-      patchRange: Object,
-      patchNum: String,
-      changeNum: String,
-      /** @type {?} */
-      changeComments: Object,
-      drafts: Object,
-      revisions: Array,
-      projectConfig: Object,
-      selectedIndex: {
-        type: Number,
-        notify: true,
-      },
-      keyEventTarget: {
-        type: Object,
-        value() { return document.body; },
-      },
-      /** @type {?} */
-      change: Object,
-      diffViewMode: {
-        type: String,
-        notify: true,
-        observer: '_updateDiffPreferences',
-      },
-      editMode: {
-        type: Boolean,
-        observer: '_editModeChanged',
-      },
-      filesExpanded: {
-        type: String,
-        value: GrFileListConstants.FilesExpandedState.NONE,
-        notify: true,
-      },
-      _filesByPath: Object,
-      _files: {
-        type: Array,
-        observer: '_filesChanged',
-        value() { return []; },
-      },
-      _loggedIn: {
-        type: Boolean,
-        value: false,
-      },
-      _reviewed: {
-        type: Array,
-        value() { return []; },
-      },
-      diffPrefs: {
-        type: Object,
-        notify: true,
-        observer: '_updateDiffPreferences',
-      },
-      /** @type {?} */
-      _userPrefs: Object,
-      _showInlineDiffs: Boolean,
-      numFilesShown: {
-        type: Number,
-        notify: true,
-      },
-      /** @type {?} */
-      _patchChange: {
-        type: Object,
-        computed: '_calculatePatchChange(_files)',
-      },
-      fileListIncrement: Number,
-      _hideChangeTotals: {
-        type: Boolean,
-        computed: '_shouldHideChangeTotals(_patchChange)',
-      },
-      _hideBinaryChangeTotals: {
-        type: Boolean,
-        computed: '_shouldHideBinaryChangeTotals(_patchChange)',
-      },
+        patchRange: Object,
+        patchNum: String,
+        changeNum: String,
+        /** @type {?} */
+        changeComments: Object,
+        drafts: Object,
+        revisions: Array,
+        projectConfig: Object,
+        selectedIndex: {
+          type: Number,
+          notify: true,
+        },
+        keyEventTarget: {
+          type: Object,
+          value() { return document.body; },
+        },
+        /** @type {?} */
+        change: Object,
+        diffViewMode: {
+          type: String,
+          notify: true,
+          observer: '_updateDiffPreferences',
+        },
+        editMode: {
+          type: Boolean,
+          observer: '_editModeChanged',
+        },
+        filesExpanded: {
+          type: String,
+          value: GrFileListConstants.FilesExpandedState.NONE,
+          notify: true,
+        },
+        _filesByPath: Object,
+        _files: {
+          type: Array,
+          observer: '_filesChanged',
+          value() { return []; },
+        },
+        _loggedIn: {
+          type: Boolean,
+          value: false,
+        },
+        _reviewed: {
+          type: Array,
+          value() { return []; },
+        },
+        diffPrefs: {
+          type: Object,
+          notify: true,
+          observer: '_updateDiffPreferences',
+        },
+        /** @type {?} */
+        _userPrefs: Object,
+        _showInlineDiffs: Boolean,
+        numFilesShown: {
+          type: Number,
+          notify: true,
+        },
+        /** @type {?} */
+        _patchChange: {
+          type: Object,
+          computed: '_calculatePatchChange(_files)',
+        },
+        fileListIncrement: Number,
+        _hideChangeTotals: {
+          type: Boolean,
+          computed: '_shouldHideChangeTotals(_patchChange)',
+        },
+        _hideBinaryChangeTotals: {
+          type: Boolean,
+          computed: '_shouldHideBinaryChangeTotals(_patchChange)',
+        },
 
-      _shownFiles: {
-        type: Array,
-        computed: '_computeFilesShown(numFilesShown, _files)',
-      },
+        _shownFiles: {
+          type: Array,
+          computed: '_computeFilesShown(numFilesShown, _files)',
+        },
 
-      /**
+        /**
        * The amount of files added to the shown files list the last time it was
        * updated. This is used for reporting the average render time.
        */
-      _reportinShownFilesIncrement: Number,
+        _reportinShownFilesIncrement: Number,
 
-      _expandedFilePaths: {
-        type: Array,
-        value() { return []; },
-      },
-      _displayLine: Boolean,
-      _loading: {
-        type: Boolean,
-        observer: '_loadingChanged',
-      },
-      /** @type {Gerrit.LayoutStats|undefined} */
-      _sizeBarLayout: {
-        type: Object,
-        computed: '_computeSizeBarLayout(_shownFiles.*)',
-      },
+        _expandedFilePaths: {
+          type: Array,
+          value() { return []; },
+        },
+        _displayLine: Boolean,
+        _loading: {
+          type: Boolean,
+          observer: '_loadingChanged',
+        },
+        /** @type {Gerrit.LayoutStats|undefined} */
+        _sizeBarLayout: {
+          type: Object,
+          computed: '_computeSizeBarLayout(_shownFiles.*)',
+        },
 
-      _showSizeBars: {
-        type: Boolean,
-        value: true,
-        computed: '_computeShowSizeBars(_userPrefs)',
-      },
+        _showSizeBars: {
+          type: Boolean,
+          value: true,
+          computed: '_computeShowSizeBars(_userPrefs)',
+        },
 
-      /** @type {Function} */
-      _cancelForEachDiff: Function,
+        /** @type {Function} */
+        _cancelForEachDiff: Function,
 
-      _showDynamicColumns: {
-        type: Boolean,
-        computed: '_computeShowDynamicColumns(_dynamicHeaderEndpoints, ' +
+        _showDynamicColumns: {
+          type: Boolean,
+          computed: '_computeShowDynamicColumns(_dynamicHeaderEndpoints, ' +
                   '_dynamicContentEndpoints, _dynamicSummaryEndpoints)',
-      },
-      /** @type {Array<string>} */
-      _dynamicHeaderEndpoints: {
-        type: Array,
-      },
-      /** @type {Array<string>} */
-      _dynamicContentEndpoints: {
-        type: Array,
-      },
-      /** @type {Array<string>} */
-      _dynamicSummaryEndpoints: {
-        type: Array,
-      },
-    },
+        },
+        /** @type {Array<string>} */
+        _dynamicHeaderEndpoints: {
+          type: Array,
+        },
+        /** @type {Array<string>} */
+        _dynamicContentEndpoints: {
+          type: Array,
+        },
+        /** @type {Array<string>} */
+        _dynamicSummaryEndpoints: {
+          type: Array,
+        },
+      };
+    }
 
-    behaviors: [
-      Gerrit.AsyncForeachBehavior,
-      Gerrit.DomUtilBehavior,
-      Gerrit.FireBehavior,
-      Gerrit.KeyboardShortcutBehavior,
-      Gerrit.PatchSetBehavior,
-      Gerrit.PathListBehavior,
-    ],
-
-    observers: [
-      '_expandedPathsChanged(_expandedFilePaths.splices)',
-      '_computeFiles(_filesByPath, changeComments, patchRange, _reviewed, ' +
+    static get observers() {
+      return [
+        '_expandedPathsChanged(_expandedFilePaths.splices)',
+        '_computeFiles(_filesByPath, changeComments, patchRange, _reviewed, ' +
           '_loading)',
-    ],
+      ];
+    }
 
-    keyBindings: {
-      esc: '_handleEscKey',
-    },
+    get keyBindings() {
+      return {
+        esc: '_handleEscKey',
+      };
+    }
 
     keyboardShortcuts() {
       return {
@@ -221,12 +234,16 @@
         [this.Shortcut.EXPAND_ALL_COMMENT_THREADS]: null,
         [this.Shortcut.COLLAPSE_ALL_COMMENT_THREADS]: null,
       };
-    },
-    listeners: {
-      keydown: '_scopedKeydownHandler',
-    },
+    }
+
+    created() {
+      super.created();
+      this.addEventListener('keydown',
+          e => this._scopedKeydownHandler(e));
+    }
 
     attached() {
+      super.attached();
       Gerrit.awaitPluginsLoaded().then(() => {
         this._dynamicHeaderEndpoints = Gerrit._endpoints.getDynamicEndpoints(
             'change-view-file-list-header');
@@ -246,11 +263,12 @@
               'Different number of dynamic file-list headers and summary.');
         }
       });
-    },
+    }
 
     detached() {
+      super.detached();
       this._cancelDiffs();
-    },
+    }
 
     /**
      * Iron-a11y-keys-behavior catches keyboard events globally. Some keyboard
@@ -264,7 +282,7 @@
         // Enter.
         this._handleOpenFile(e);
       }
-    },
+    }
 
     reload() {
       if (!this.changeNum || !this.patchRange.patchNum) {
@@ -302,23 +320,23 @@
         this._detectChromiteButler();
         this.$.reporting.fileListDisplayed();
       });
-    },
+    }
 
     _detectChromiteButler() {
       const hasButler = !!document.getElementById('butler-suggested-owners');
       if (hasButler) {
         this.$.reporting.reportExtension('butler');
       }
-    },
+    }
 
     get diffs() {
       return Array.from(
           Polymer.dom(this.root).querySelectorAll('gr-diff-host'));
-    },
+    }
 
     openDiffPrefs() {
       this.$.diffPreferencesDialog.open();
-    },
+    }
 
     _calculatePatchChange(files) {
       const magicFilesExcluded = files.filter(files => {
@@ -343,15 +361,15 @@
         };
       }, {inserted: 0, deleted: 0, size_delta_inserted: 0,
         size_delta_deleted: 0, total_size: 0});
-    },
+    }
 
     _getDiffPreferences() {
       return this.$.restAPI.getDiffPreferences();
-    },
+    }
 
     _getPreferences() {
       return this.$.restAPI.getPreferences();
-    },
+    }
 
     _togglePathExpanded(path) {
       // Is the path in the list of expanded diffs? IF so remove it, otherwise
@@ -362,11 +380,11 @@
       } else {
         this.splice('_expandedFilePaths', pathIndex, 1);
       }
-    },
+    }
 
     _togglePathExpandedByIndex(index) {
       this._togglePathExpanded(this._files[index].__path);
-    },
+    }
 
     _updateDiffPreferences() {
       if (!this.diffs.length) { return; }
@@ -374,14 +392,14 @@
       this.$.reporting.time(EXPAND_ALL_TIMING_LABEL);
       this._renderInOrder(this._expandedFilePaths, this.diffs,
           this._expandedFilePaths.length);
-    },
+    }
 
     _forEachDiff(fn) {
       const diffs = this.diffs;
       for (let i = 0; i < diffs.length; i++) {
         fn(diffs[i]);
       }
-    },
+    }
 
     expandAllDiffs() {
       this._showInlineDiffs = true;
@@ -398,7 +416,7 @@
       }
 
       this.splice(...['_expandedFilePaths', 0, 0].concat(newPaths));
-    },
+    }
 
     collapseAllDiffs() {
       this._showInlineDiffs = false;
@@ -406,7 +424,7 @@
       this.filesExpanded = this._computeExpandedFiles(
           this._expandedFilePaths.length, this._files.length);
       this.$.diffCursor.handleDiffUpdate();
-    },
+    }
 
     /**
      * Computes a string with the number of comments and unresolved comments.
@@ -433,7 +451,7 @@
           (commentString && unresolvedString ? ' ' : '') +
           // Add parentheses around unresolved if it exists.
           (unresolvedString ? `(${unresolvedString})` : '');
-    },
+    }
 
     /**
      * Computes a string with the number of drafts.
@@ -448,7 +466,7 @@
           changeComments.computeDraftCount(patchRange.basePatchNum, path) +
           changeComments.computeDraftCount(patchRange.patchNum, path);
       return GrCountStringFormatter.computePluralString(draftCount, 'draft');
-    },
+    }
 
     /**
      * Computes a shortened string with the number of drafts.
@@ -463,7 +481,7 @@
           changeComments.computeDraftCount(patchRange.basePatchNum, path) +
           changeComments.computeDraftCount(patchRange.patchNum, path);
       return GrCountStringFormatter.computeShortString(draftCount, 'd');
-    },
+    }
 
     /**
      * Computes a shortened string with the number of comments.
@@ -478,7 +496,7 @@
           changeComments.computeCommentCount(patchRange.basePatchNum, path) +
           changeComments.computeCommentCount(patchRange.patchNum, path);
       return GrCountStringFormatter.computeShortString(commentCount, 'c');
-    },
+    }
 
     /**
      * @param {string} path
@@ -495,27 +513,27 @@
       }
 
       this._saveReviewedState(path, reviewed);
-    },
+    }
 
     _saveReviewedState(path, reviewed) {
       return this.$.restAPI.saveFileReviewed(this.changeNum,
           this.patchRange.patchNum, path, reviewed);
-    },
+    }
 
     _getLoggedIn() {
       return this.$.restAPI.getLoggedIn();
-    },
+    }
 
     _getReviewedFiles() {
       if (this.editMode) { return Promise.resolve([]); }
       return this.$.restAPI.getReviewedFiles(this.changeNum,
           this.patchRange.patchNum);
-    },
+    }
 
     _getFiles() {
       return this.$.restAPI.getChangeOrEditFiles(
           this.changeNum, this.patchRange);
-    },
+    }
 
     /**
      * The closure compiler doesn't realize this.specialFilePathCompare is
@@ -534,7 +552,7 @@
         files.push(info);
       }
       return files;
-    },
+    }
 
     /**
      * Handle all events from the file list dom-repeat so event handleers don't
@@ -564,7 +582,7 @@
 
       e.preventDefault();
       this._togglePathExpanded(path);
-    },
+    }
 
     _handleLeftPane(e) {
       if (this.shouldSuppressKeyboardShortcut(e) || this._noDiffsExpanded()) {
@@ -573,7 +591,7 @@
 
       e.preventDefault();
       this.$.diffCursor.moveLeft();
-    },
+    }
 
     _handleRightPane(e) {
       if (this.shouldSuppressKeyboardShortcut(e) || this._noDiffsExpanded()) {
@@ -582,7 +600,7 @@
 
       e.preventDefault();
       this.$.diffCursor.moveRight();
-    },
+    }
 
     _handleToggleInlineDiff(e) {
       if (this.shouldSuppressKeyboardShortcut(e) ||
@@ -591,14 +609,14 @@
 
       e.preventDefault();
       this._togglePathExpandedByIndex(this.$.fileCursor.index);
-    },
+    }
 
     _handleToggleAllInlineDiffs(e) {
       if (this.shouldSuppressKeyboardShortcut(e)) { return; }
 
       e.preventDefault();
       this._toggleInlineDiffs();
-    },
+    }
 
     _handleCursorNext(e) {
       if (this.shouldSuppressKeyboardShortcut(e) || this.modifierPressed(e)) {
@@ -616,7 +634,7 @@
         this.$.fileCursor.next();
         this.selectedIndex = this.$.fileCursor.index;
       }
-    },
+    }
 
     _handleCursorPrev(e) {
       if (this.shouldSuppressKeyboardShortcut(e) || this.modifierPressed(e)) {
@@ -634,14 +652,14 @@
         this.$.fileCursor.previous();
         this.selectedIndex = this.$.fileCursor.index;
       }
-    },
+    }
 
     _handleNewComment(e) {
       if (this.shouldSuppressKeyboardShortcut(e) ||
           this.modifierPressed(e)) { return; }
       e.preventDefault();
       this.$.diffCursor.createCommentInPlace();
-    },
+    }
 
     _handleOpenLastFile(e) {
       // Check for meta key to avoid overriding native chrome shortcut.
@@ -650,7 +668,7 @@
 
       e.preventDefault();
       this._openSelectedFile(this._files.length - 1);
-    },
+    }
 
     _handleOpenFirstFile(e) {
       // Check for meta key to avoid overriding native chrome shortcut.
@@ -659,7 +677,7 @@
 
       e.preventDefault();
       this._openSelectedFile(0);
-    },
+    }
 
     _handleOpenFile(e) {
       if (this.shouldSuppressKeyboardShortcut(e) ||
@@ -672,7 +690,7 @@
       }
 
       this._openSelectedFile();
-    },
+    }
 
     _handleNextChunk(e) {
       if (this.shouldSuppressKeyboardShortcut(e) ||
@@ -687,7 +705,7 @@
       } else {
         this.$.diffCursor.moveToNextChunk();
       }
-    },
+    }
 
     _handlePrevChunk(e) {
       if (this.shouldSuppressKeyboardShortcut(e) ||
@@ -702,7 +720,7 @@
       } else {
         this.$.diffCursor.moveToPreviousChunk();
       }
-    },
+    }
 
     _handleToggleFileReviewed(e) {
       if (this.shouldSuppressKeyboardShortcut(e) || this.modifierPressed(e)) {
@@ -712,7 +730,7 @@
       e.preventDefault();
       if (!this._files[this.$.fileCursor.index]) { return; }
       this._reviewFile(this._files[this.$.fileCursor.index].__path);
-    },
+    }
 
     _handleToggleLeftPane(e) {
       if (this.shouldSuppressKeyboardShortcut(e)) { return; }
@@ -721,7 +739,7 @@
       this._forEachDiff(diff => {
         diff.toggleLeftDiff();
       });
-    },
+    }
 
     _toggleInlineDiffs() {
       if (this._showInlineDiffs) {
@@ -729,13 +747,13 @@
       } else {
         this.expandAllDiffs();
       }
-    },
+    }
 
     _openCursorFile() {
       const diff = this.$.diffCursor.getTargetDiffElement();
       Gerrit.Nav.navigateToDiff(this.change, diff.path,
           diff.patchRange.patchNum, this.patchRange.basePatchNum);
-    },
+    }
 
     /**
      * @param {number=} opt_index
@@ -748,7 +766,7 @@
       Gerrit.Nav.navigateToDiff(this.change,
           this._files[this.$.fileCursor.index].__path, this.patchRange.patchNum,
           this.patchRange.basePatchNum);
-    },
+    }
 
     _addDraftAtTarget() {
       const diff = this.$.diffCursor.getTargetDiffElement();
@@ -756,20 +774,20 @@
       if (diff && target) {
         diff.addDraftAtLine(target);
       }
-    },
+    }
 
     _shouldHideChangeTotals(_patchChange) {
       return _patchChange.inserted === 0 && _patchChange.deleted === 0;
-    },
+    }
 
     _shouldHideBinaryChangeTotals(_patchChange) {
       return _patchChange.size_delta_inserted === 0 &&
           _patchChange.size_delta_deleted === 0;
-    },
+    }
 
     _computeFileStatus(status) {
       return status || 'M';
-    },
+    }
 
     _computeDiffURL(change, patchNum, basePatchNum, path, editMode) {
       // Polymer 2: check for undefined
@@ -784,7 +802,7 @@
             basePatchNum);
       }
       return Gerrit.Nav.getUrlForDiff(change, path, patchNum, basePatchNum);
-    },
+    }
 
     _formatBytes(bytes) {
       if (bytes == 0) return '+/-0 B';
@@ -796,7 +814,7 @@
       const prepend = bytes > 0 ? '+' : '';
       return prepend + parseFloat((bytes / Math.pow(bits, exponent))
           .toFixed(decimals)) + ' ' + sizes[exponent];
-    },
+    }
 
     _formatPercentage(size, delta) {
       const oldSize = size - delta;
@@ -805,12 +823,12 @@
 
       const percentage = Math.round(Math.abs(delta * 100 / oldSize));
       return '(' + (delta > 0 ? '+' : '-') + percentage + '%)';
-    },
+    }
 
     _computeBinaryClass(delta) {
       if (delta === 0) { return; }
       return delta >= 0 ? 'added' : 'removed';
-    },
+    }
 
     /**
      * @param {string} baseClass
@@ -825,16 +843,16 @@
         classes.push('invisible');
       }
       return classes.join(' ');
-    },
+    }
 
     _computePathClass(path, expandedFilesRecord) {
       return this._isFileExpanded(path, expandedFilesRecord) ? 'expanded' : '';
-    },
+    }
 
     _computeShowHideIcon(path, expandedFilesRecord) {
       return this._isFileExpanded(path, expandedFilesRecord) ?
         'gr-icons:expand-less' : 'gr-icons:expand-more';
-    },
+    }
 
     _computeFiles(filesByPath, changeComments, patchRange, reviewed, loading) {
       // Polymer 2: check for undefined
@@ -864,7 +882,7 @@
       }
 
       this._files = this._normalizeChangeFilesResponse(files);
-    },
+    }
 
     _computeFilesShown(numFilesShown, files) {
       // Polymer 2: check for undefined
@@ -888,13 +906,13 @@
           Math.max(0, filesShown.length - previousNumFilesShown);
 
       return filesShown;
-    },
+    }
 
     _updateDiffCursor() {
       // Overwrite the cursor's list of diffs:
       this.$.diffCursor.splice(
           ...['diffs', 0, this.$.diffCursor.diffs.length].concat(this.diffs));
-    },
+    }
 
     _filesChanged() {
       if (this._files && this._files.length > 0) {
@@ -904,41 +922,41 @@
         this.$.fileCursor.stops = files;
         this.$.fileCursor.setCursorAtIndex(this.selectedIndex, true);
       }
-    },
+    }
 
     _incrementNumFilesShown() {
       this.numFilesShown += this.fileListIncrement;
-    },
+    }
 
     _computeFileListControlClass(numFilesShown, files) {
       return numFilesShown >= files.length ? 'invisible' : '';
-    },
+    }
 
     _computeIncrementText(numFilesShown, files) {
       if (!files) { return ''; }
       const text =
           Math.min(this.fileListIncrement, files.length - numFilesShown);
       return 'Show ' + text + ' more';
-    },
+    }
 
     _computeShowAllText(files) {
       if (!files) { return ''; }
       return 'Show all ' + files.length + ' files';
-    },
+    }
 
     _computeWarnShowAll(files) {
       return files.length > WARN_SHOW_ALL_THRESHOLD;
-    },
+    }
 
     _computeShowAllWarning(files) {
       if (!this._computeWarnShowAll(files)) { return ''; }
       return 'Warning: showing all ' + files.length +
           ' files may take several seconds.';
-    },
+    }
 
     _showAllFiles() {
       this.numFilesShown = this._files.length;
-    },
+    }
 
     _computePatchSetDescription(revisions, patchNum) {
       // Polymer 2: check for undefined
@@ -949,7 +967,7 @@
       const rev = this.getRevisionByPatchNum(revisions, patchNum);
       return (rev && rev.description) ?
         rev.description.substring(0, PATCH_DESC_MAX_LENGTH) : '';
-    },
+    }
 
     /**
      * Get a descriptive label for use in the status indicator's tooltip and
@@ -961,16 +979,16 @@
       const statusCode = this._computeFileStatus(status);
       return FileStatus.hasOwnProperty(statusCode) ?
         FileStatus[statusCode] : 'Status Unknown';
-    },
+    }
 
     _isFileExpanded(path, expandedFilesRecord) {
       return expandedFilesRecord.base.includes(path);
-    },
+    }
 
     _onLineSelected(e, detail) {
       this.$.diffCursor.moveToLineNumber(detail.number, detail.side,
           detail.path);
-    },
+    }
 
     _computeExpandedFiles(expandedCount, totalCount) {
       if (expandedCount === 0) {
@@ -979,7 +997,7 @@
         return GrFileListConstants.FilesExpandedState.ALL;
       }
       return GrFileListConstants.FilesExpandedState.SOME;
-    },
+    }
 
     /**
      * Handle splices to the list of expanded file paths. If there are any new
@@ -1017,14 +1035,14 @@
 
       this._updateDiffCursor();
       this.$.diffCursor.handleDiffUpdate();
-    },
+    }
 
     _clearCollapsedDiffs(collapsedDiffs) {
       for (const diff of collapsedDiffs) {
         diff.cancel();
         diff.clearDiffContent();
       }
-    },
+    }
 
     /**
      * Given an array of paths and a NodeList of diff elements, render the diff
@@ -1065,13 +1083,13 @@
           this.$.diffCursor.handleDiffUpdate();
         });
       });
-    },
+    }
 
     /** Cancel the rendering work of every diff in the list */
     _cancelDiffs() {
       if (this._cancelForEachDiff) { this._cancelForEachDiff(); }
       this._forEachDiff(d => d.cancel());
-    },
+    }
 
     /**
      * In the given NodeList of diff elements, find the diff for the given path.
@@ -1085,7 +1103,7 @@
           return diffElements[i];
         }
       }
-    },
+    }
 
     /**
      * Reset the comments of a modified thread
@@ -1122,14 +1140,14 @@
       });
       Polymer.dom.flush();
       return;
-    },
+    }
 
     _handleEscKey(e) {
       if (this.shouldSuppressKeyboardShortcut(e) ||
           this.modifierPressed(e)) { return; }
       e.preventDefault();
       this._displayLine = false;
-    },
+    }
 
     /**
      * Update the loading class for the file list rows. The update is inside a
@@ -1143,19 +1161,19 @@
         // this way, the gray loading style is not shown on initial loads.
         this.classList.toggle('loading', loading && !!this._files.length);
       }, LOADING_DEBOUNCE_INTERVAL);
-    },
+    }
 
     _editModeChanged(editMode) {
       this.classList.toggle('editMode', editMode);
-    },
+    }
 
     _computeReviewedClass(isReviewed) {
       return isReviewed ? 'isReviewed' : '';
-    },
+    }
 
     _computeReviewedText(isReviewed) {
       return isReviewed ? 'MARK UNREVIEWED' : 'MARK REVIEWED';
-    },
+    }
 
     /**
      * Given a file path, return whether that path should have visible size bars
@@ -1165,7 +1183,7 @@
      */
     _showBarsForPath(path) {
       return path !== this.COMMIT_MESSAGE_PATH && path !== this.MERGE_LIST_PATH;
-    },
+    }
 
     /**
      * Compute size bar layout values from the file list.
@@ -1199,7 +1217,7 @@
         stats.deletionOffset = stats.maxAdditionWidth + SIZE_BAR_GAP_WIDTH;
       }
       return stats;
-    },
+    }
 
     /**
      * Get the width of the addition bar for a file.
@@ -1216,7 +1234,7 @@
       const width =
           stats.maxAdditionWidth * file.lines_inserted / stats.maxInserted;
       return width === 0 ? 0 : Math.max(SIZE_BAR_MIN_WIDTH, width);
-    },
+    }
 
     /**
      * Get the x-offset of the addition bar for a file.
@@ -1227,7 +1245,7 @@
     _computeBarAdditionX(file, stats) {
       return stats.maxAdditionWidth -
           this._computeBarAdditionWidth(file, stats);
-    },
+    }
 
     /**
      * Get the width of the deletion bar for a file.
@@ -1244,7 +1262,7 @@
       const width =
           stats.maxDeletionWidth * file.lines_deleted / stats.maxDeleted;
       return width === 0 ? 0 : Math.max(SIZE_BAR_MIN_WIDTH, width);
-    },
+    }
 
     /**
      * Get the x-offset of the deletion bar for a file.
@@ -1253,11 +1271,11 @@
      */
     _computeBarDeletionX(stats) {
       return stats.deletionOffset;
-    },
+    }
 
     _computeShowSizeBars(userPrefs) {
       return !!userPrefs.size_bar_in_change_table;
-    },
+    }
 
     _computeSizeBarsClass(showSizeBars, path) {
       let hideClass = '';
@@ -1267,7 +1285,7 @@
         hideClass = 'invisible';
       }
       return `sizeBars desktop ${hideClass}`;
-    },
+    }
 
     /**
      * Shows registered dynamic columns iff the 'header', 'content' and
@@ -1280,7 +1298,7 @@
       return headerEndpoints && contentEndpoints && summaryEndpoints &&
              headerEndpoints.length === contentEndpoints.length &&
              headerEndpoints.length === summaryEndpoints.length;
-    },
+    }
 
     /**
      * Returns true if none of the inline diffs have been expanded.
@@ -1288,7 +1306,7 @@
      */
     _noDiffsExpanded() {
       return this.filesExpanded === GrFileListConstants.FilesExpandedState.NONE;
-    },
+    }
 
     /**
      * Method to call via binding when each file list row is rendered. This
@@ -1305,7 +1323,7 @@
         }, 1);
       }
       return '';
-    },
+    }
 
     _reviewedTitle(reviewed) {
       if (reviewed) {
@@ -1313,12 +1331,14 @@
       }
 
       return 'Mark as reviewed (shortcut: r)';
-    },
+    }
 
     _handleReloadingDiffPreference() {
       this._getDiffPreferences().then(prefs => {
         this.diffPrefs = prefs;
       });
-    },
-  });
+    }
+  }
+
+  customElements.define(GrFileList.is, GrFileList);
 })();
diff --git a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list_test.html b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list_test.html
index 05b23fa..0d25846 100644
--- a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list_test.html
+++ b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-file-list</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>
diff --git a/polygerrit-ui/app/elements/change/gr-included-in-dialog/gr-included-in-dialog.js b/polygerrit-ui/app/elements/change/gr-included-in-dialog/gr-included-in-dialog.js
index 4b8ce22..f0cba0b 100644
--- a/polygerrit-ui/app/elements/change/gr-included-in-dialog/gr-included-in-dialog.js
+++ b/polygerrit-ui/app/elements/change/gr-included-in-dialog/gr-included-in-dialog.js
@@ -17,36 +17,40 @@
 (function() {
   'use strict';
 
-  Polymer({
-    is: 'gr-included-in-dialog',
-
+  /**
+    * @appliesMixin Gerrit.FireMixin
+    */
+  class GrIncludedInDialog extends Polymer.mixinBehaviors( [
+    Gerrit.FireBehavior,
+  ], Polymer.GestureEventListeners(
+      Polymer.LegacyElementMixin(
+          Polymer.Element))) {
+    static get is() { return 'gr-included-in-dialog'; }
     /**
      * Fired when the user presses the close button.
      *
      * @event close
      */
 
-    properties: {
+    static get properties() {
+      return {
       /** @type {?} */
-      changeNum: {
-        type: Object,
-        observer: '_resetData',
-      },
-      /** @type {?} */
-      _includedIn: Object,
-      _loaded: {
-        type: Boolean,
-        value: false,
-      },
-      _filterText: {
-        type: String,
-        value: '',
-      },
-    },
-
-    behaviors: [
-      Gerrit.FireBehavior,
-    ],
+        changeNum: {
+          type: Object,
+          observer: '_resetData',
+        },
+        /** @type {?} */
+        _includedIn: Object,
+        _loaded: {
+          type: Boolean,
+          value: false,
+        },
+        _filterText: {
+          type: String,
+          value: '',
+        },
+      };
+    }
 
     loadData() {
       if (!this.changeNum) { return; }
@@ -57,12 +61,12 @@
             this._includedIn = configs;
             this._loaded = true;
           });
-    },
+    }
 
     _resetData() {
       this._includedIn = null;
       this._loaded = false;
-    },
+    }
 
     _computeGroups(includedIn, filterText) {
       if (!includedIn) { return []; }
@@ -83,22 +87,24 @@
         }
       }
       return groups.filter(g => g.items.length);
-    },
+    }
 
     _handleCloseTap(e) {
       e.preventDefault();
       e.stopPropagation();
       this.fire('close', null, {bubbles: false});
-    },
+    }
 
     _computeLoadingClass(loaded) {
       return loaded ? 'loading loaded' : 'loading';
-    },
+    }
 
     _onFilterChanged() {
       this.debounce('filter-change', () => {
         this._filterText = this.$.filterInput.bindValue;
       }, 100);
-    },
-  });
+    }
+  }
+
+  customElements.define(GrIncludedInDialog.is, GrIncludedInDialog);
 })();
diff --git a/polygerrit-ui/app/elements/change/gr-included-in-dialog/gr-included-in-dialog_test.html b/polygerrit-ui/app/elements/change/gr-included-in-dialog/gr-included-in-dialog_test.html
index 68c77e6..b97e0b4 100644
--- a/polygerrit-ui/app/elements/change/gr-included-in-dialog/gr-included-in-dialog_test.html
+++ b/polygerrit-ui/app/elements/change/gr-included-in-dialog/gr-included-in-dialog_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-included-in-dialog</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>
diff --git a/polygerrit-ui/app/elements/change/gr-label-score-row/gr-label-score-row.js b/polygerrit-ui/app/elements/change/gr-label-score-row/gr-label-score-row.js
index bd7fff8..abfe469 100644
--- a/polygerrit-ui/app/elements/change/gr-label-score-row/gr-label-score-row.js
+++ b/polygerrit-ui/app/elements/change/gr-label-score-row/gr-label-score-row.js
@@ -17,56 +17,59 @@
 (function() {
   'use strict';
 
-  Polymer({
-    is: 'gr-label-score-row',
-
+  class GrLabelScoreRow extends Polymer.GestureEventListeners(
+      Polymer.LegacyElementMixin(
+          Polymer.Element)) {
+    static get is() { return 'gr-label-score-row'; }
     /**
      * Fired when any label is changed.
      *
      * @event labels-changed
      */
 
-    properties: {
+    static get properties() {
+      return {
       /**
        * @type {{ name: string }}
        */
-      label: Object,
-      labels: Object,
-      name: {
-        type: String,
-        reflectToAttribute: true,
-      },
-      permittedLabels: Object,
-      labelValues: Object,
-      _selectedValueText: {
-        type: String,
-        value: 'No value selected',
-      },
-      _items: {
-        type: Array,
-        computed: '_computePermittedLabelValues(permittedLabels, label.name)',
-      },
-    },
+        label: Object,
+        labels: Object,
+        name: {
+          type: String,
+          reflectToAttribute: true,
+        },
+        permittedLabels: Object,
+        labelValues: Object,
+        _selectedValueText: {
+          type: String,
+          value: 'No value selected',
+        },
+        _items: {
+          type: Array,
+          computed: '_computePermittedLabelValues(permittedLabels, label.name)',
+        },
+      };
+    }
 
     get selectedItem() {
       if (!this._ironSelector) { return undefined; }
       return this._ironSelector.selectedItem;
-    },
+    }
 
     get selectedValue() {
       if (!this._ironSelector) { return undefined; }
       return this._ironSelector.selected;
-    },
+    }
 
     setSelectedValue(value) {
       // The selector may not be present if it’s not at the latest patch set.
       if (!this._ironSelector) { return; }
       this._ironSelector.select(value);
-    },
+    }
 
     get _ironSelector() {
       return this.$ && this.$.labelSelector;
-    },
+    }
 
     _computeBlankItems(permittedLabels, label, side) {
       if (!permittedLabels || !permittedLabels[label] ||
@@ -82,7 +85,7 @@
       const endPosition = this.labelValues[parseInt(
           permittedLabels[label][permittedLabels[label].length - 1], 10)];
       return new Array(Object.keys(this.labelValues).length - endPosition - 1);
-    },
+    }
 
     _getLabelValue(labels, permittedLabels, label) {
       if (label.value) {
@@ -93,7 +96,7 @@
         return permittedLabels[label.name].find(
             value => parseInt(value, 10) === labels[label.name].default_value);
       }
-    },
+    }
 
     _computeButtonClass(value, index, totalItems) {
       const classes = [];
@@ -114,7 +117,7 @@
       }
 
       return classes.join(' ');
-    },
+    }
 
     _computeLabelValue(labels, permittedLabels, label) {
       if ([labels, permittedLabels, label].some(arg => arg === undefined)) {
@@ -131,7 +134,7 @@
         }
       }
       return null;
-    },
+    }
 
     _setSelectedValueText(e) {
       // Needed because when the selected item changes, it first changes to
@@ -145,17 +148,17 @@
       this.dispatchEvent(new CustomEvent(
           'labels-changed',
           {detail: {name, value}, bubbles: true, composed: true}));
-    },
+    }
 
     _computeAnyPermittedLabelValues(permittedLabels, label) {
       return permittedLabels && permittedLabels.hasOwnProperty(label) &&
         permittedLabels[label].length;
-    },
+    }
 
     _computeHiddenClass(permittedLabels, label) {
       return !this._computeAnyPermittedLabelValues(permittedLabels, label) ?
         'hidden' : '';
-    },
+    }
 
     _computePermittedLabelValues(permittedLabels, label) {
       // Polymer 2: check for undefined
@@ -164,12 +167,14 @@
       }
 
       return permittedLabels[label];
-    },
+    }
 
     _computeLabelValueTitle(labels, label, value) {
       return labels[label] &&
         labels[label].values &&
         labels[label].values[value];
-    },
-  });
+    }
+  }
+
+  customElements.define(GrLabelScoreRow.is, GrLabelScoreRow);
 })();
diff --git a/polygerrit-ui/app/elements/change/gr-label-score-row/gr-label-score-row_test.html b/polygerrit-ui/app/elements/change/gr-label-score-row/gr-label-score-row_test.html
index f1a009a..e9c21bc 100644
--- a/polygerrit-ui/app/elements/change/gr-label-score-row/gr-label-score-row_test.html
+++ b/polygerrit-ui/app/elements/change/gr-label-score-row/gr-label-score-row_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-label-score-row</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>
diff --git a/polygerrit-ui/app/elements/change/gr-label-scores/gr-label-scores.js b/polygerrit-ui/app/elements/change/gr-label-scores/gr-label-scores.js
index 9424907..2c164f1 100644
--- a/polygerrit-ui/app/elements/change/gr-label-scores/gr-label-scores.js
+++ b/polygerrit-ui/app/elements/change/gr-label-scores/gr-label-scores.js
@@ -17,25 +17,29 @@
 (function() {
   'use strict';
 
-  Polymer({
-    is: 'gr-label-scores',
+  class GrLabelScores extends Polymer.GestureEventListeners(
+      Polymer.LegacyElementMixin(
+          Polymer.Element)) {
+    static get is() { return 'gr-label-scores'; }
 
-    properties: {
-      _labels: {
-        type: Array,
-        computed: '_computeLabels(change.labels.*, account)',
-      },
-      permittedLabels: {
-        type: Object,
-        observer: '_computeColumns',
-      },
-      /** @type {?} */
-      change: Object,
-      /** @type {?} */
-      account: Object,
+    static get properties() {
+      return {
+        _labels: {
+          type: Array,
+          computed: '_computeLabels(change.labels.*, account)',
+        },
+        permittedLabels: {
+          type: Object,
+          observer: '_computeColumns',
+        },
+        /** @type {?} */
+        change: Object,
+        /** @type {?} */
+        account: Object,
 
-      _labelValues: Object,
-    },
+        _labelValues: Object,
+      };
+    }
 
     getLabelValues() {
       const labels = {};
@@ -61,7 +65,7 @@
         }
       }
       return labels;
-    },
+    }
 
     _getStringLabelValue(labels, labelName, numberValue) {
       for (const k in labels[labelName].values) {
@@ -70,7 +74,7 @@
         }
       }
       return numberValue;
-    },
+    }
 
     _getVoteForAccount(labels, labelName, account) {
       const votes = labels[labelName];
@@ -83,7 +87,7 @@
         }
       }
       return null;
-    },
+    }
 
     _computeLabels(labelRecord, account) {
       // Polymer 2: check for undefined
@@ -99,7 +103,7 @@
           value: this._getVoteForAccount(labelsObj, key, this.account),
         };
       });
-    },
+    }
 
     _computeColumns(permittedLabels) {
       const labels = Object.keys(permittedLabels);
@@ -118,11 +122,11 @@
         values[orderedValues[i]] = i;
       }
       this._labelValues = values;
-    },
+    }
 
     _changeIsMerged(changeStatus) {
       return changeStatus === 'MERGED';
-    },
+    }
 
     /**
      * @param label {string|undefined}
@@ -136,6 +140,8 @@
 
       return permittedLabels.hasOwnProperty(label) &&
         permittedLabels[label].length ? 'access' : 'no-access';
-    },
-  });
+    }
+  }
+
+  customElements.define(GrLabelScores.is, GrLabelScores);
 })();
diff --git a/polygerrit-ui/app/elements/change/gr-label-scores/gr-label-scores_test.html b/polygerrit-ui/app/elements/change/gr-label-scores/gr-label-scores_test.html
index b8d471c..4c489b7 100644
--- a/polygerrit-ui/app/elements/change/gr-label-scores/gr-label-scores_test.html
+++ b/polygerrit-ui/app/elements/change/gr-label-scores/gr-label-scores_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-label-scores</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>
diff --git a/polygerrit-ui/app/elements/change/gr-message/gr-message.js b/polygerrit-ui/app/elements/change/gr-message/gr-message.js
index 26e0cd3..7c1d66c 100644
--- a/polygerrit-ui/app/elements/change/gr-message/gr-message.js
+++ b/polygerrit-ui/app/elements/change/gr-message/gr-message.js
@@ -20,9 +20,15 @@
   const PATCH_SET_PREFIX_PATTERN = /^Patch Set \d+: /;
   const LABEL_TITLE_SCORE_PATTERN = /^([A-Za-z0-9-]+)([+-]\d+)$/;
 
-  Polymer({
-    is: 'gr-message',
-
+  /**
+    * @appliesMixin Gerrit.FireMixin
+    */
+  class GrMessage extends Polymer.mixinBehaviors( [
+    Gerrit.FireBehavior,
+  ], Polymer.GestureEventListeners(
+      Polymer.LegacyElementMixin(
+          Polymer.Element))) {
+    static get is() { return 'gr-message'; }
     /**
      * Fired when this message's reply link is tapped.
      *
@@ -35,90 +41,93 @@
      * @event message-anchor-tap
      */
 
-    listeners: {
-      click: '_handleClick',
-    },
+    static get properties() {
+      return {
+        changeNum: Number,
+        /** @type {?} */
+        message: Object,
+        author: {
+          type: Object,
+          computed: '_computeAuthor(message)',
+        },
+        comments: {
+          type: Object,
+          observer: '_commentsChanged',
+        },
+        config: Object,
+        hideAutomated: {
+          type: Boolean,
+          value: false,
+        },
+        hidden: {
+          type: Boolean,
+          computed: '_computeIsHidden(hideAutomated, isAutomated)',
+          reflectToAttribute: true,
+        },
+        isAutomated: {
+          type: Boolean,
+          computed: '_computeIsAutomated(message)',
+        },
+        showAvatar: {
+          type: Boolean,
+          computed: '_computeShowAvatar(author, config)',
+        },
+        showOnBehalfOf: {
+          type: Boolean,
+          computed: '_computeShowOnBehalfOf(message)',
+        },
+        showReplyButton: {
+          type: Boolean,
+          computed: '_computeShowReplyButton(message, _loggedIn)',
+        },
+        projectName: {
+          type: String,
+          observer: '_projectNameChanged',
+        },
 
-    properties: {
-      changeNum: Number,
-      /** @type {?} */
-      message: Object,
-      author: {
-        type: Object,
-        computed: '_computeAuthor(message)',
-      },
-      comments: {
-        type: Object,
-        observer: '_commentsChanged',
-      },
-      config: Object,
-      hideAutomated: {
-        type: Boolean,
-        value: false,
-      },
-      hidden: {
-        type: Boolean,
-        computed: '_computeIsHidden(hideAutomated, isAutomated)',
-        reflectToAttribute: true,
-      },
-      isAutomated: {
-        type: Boolean,
-        computed: '_computeIsAutomated(message)',
-      },
-      showAvatar: {
-        type: Boolean,
-        computed: '_computeShowAvatar(author, config)',
-      },
-      showOnBehalfOf: {
-        type: Boolean,
-        computed: '_computeShowOnBehalfOf(message)',
-      },
-      showReplyButton: {
-        type: Boolean,
-        computed: '_computeShowReplyButton(message, _loggedIn)',
-      },
-      projectName: {
-        type: String,
-        observer: '_projectNameChanged',
-      },
-
-      /**
+        /**
        * A mapping from label names to objects representing the minimum and
        * maximum possible values for that label.
        */
-      labelExtremes: Object,
+        labelExtremes: Object,
 
-      /**
+        /**
        * @type {{ commentlinks: Array }}
        */
-      _projectConfig: Object,
-      // Computed property needed to trigger Polymer value observing.
-      _expanded: {
-        type: Object,
-        computed: '_computeExpanded(message.expanded)',
-      },
-      _loggedIn: {
-        type: Boolean,
-        value: false,
-      },
-    },
+        _projectConfig: Object,
+        // Computed property needed to trigger Polymer value observing.
+        _expanded: {
+          type: Object,
+          computed: '_computeExpanded(message.expanded)',
+        },
+        _loggedIn: {
+          type: Boolean,
+          value: false,
+        },
+      };
+    }
 
-    behaviors: [
-      Gerrit.FireBehavior,
-    ],
+    static get observers() {
+      return [
+        '_updateExpandedClass(message.expanded)',
+      ];
+    }
 
-    observers: [
-      '_updateExpandedClass(message.expanded)',
-    ],
+    created() {
+      super.created();
+      this.addEventListener('click',
+          e => this._handleClick(e));
+    }
 
     ready() {
+      super.ready();
       this.$.restAPI.getConfig().then(config => {
         this.config = config;
       });
       this.$.restAPI.getLoggedIn().then(loggedIn => {
         this._loggedIn = loggedIn;
       });
-    },
+    }
 
     _updateExpandedClass(expanded) {
       if (expanded) {
@@ -126,30 +135,30 @@
       } else {
         this.classList.remove('expanded');
       }
-    },
+    }
 
     _computeAuthor(message) {
       return message.author || message.updated_by;
-    },
+    }
 
     _computeShowAvatar(author, config) {
       return !!(author && config && config.plugin && config.plugin.has_avatars);
-    },
+    }
 
     _computeShowOnBehalfOf(message) {
       const author = message.author || message.updated_by;
       return !!(author && message.real_author &&
           author._account_id != message.real_author._account_id);
-    },
+    }
 
     _computeShowReplyButton(message, loggedIn) {
       return message && !!message.message && loggedIn &&
           !this._computeIsAutomated(message);
-    },
+    }
 
     _computeExpanded(expanded) {
       return expanded;
-    },
+    }
 
     /**
      * If there is no value set on the message object as to whether _expanded
@@ -160,33 +169,33 @@
       if (this.message && this.message.expanded === undefined) {
         this.set('message.expanded', Object.keys(value || {}).length > 0);
       }
-    },
+    }
 
     _handleClick(e) {
       if (this.message.expanded) { return; }
       e.stopPropagation();
       this.set('message.expanded', true);
-    },
+    }
 
     _handleAuthorClick(e) {
       if (!this.message.expanded) { return; }
       e.stopPropagation();
       this.set('message.expanded', false);
-    },
+    }
 
     _computeIsAutomated(message) {
       return !!(message.reviewer ||
           this._computeIsReviewerUpdate(message) ||
           (message.tag && message.tag.startsWith('autogenerated')));
-    },
+    }
 
     _computeIsHidden(hideAutomated, isAutomated) {
       return hideAutomated && isAutomated;
-    },
+    }
 
     _computeIsReviewerUpdate(event) {
       return event.type === 'REVIEWER_UPDATE';
-    },
+    }
 
     _getScores(message) {
       if (!message.message) { return []; }
@@ -199,7 +208,7 @@
           .map(s => s.match(LABEL_TITLE_SCORE_PATTERN))
           .filter(ms => ms && ms.length === 3)
           .map(ms => ({label: ms[1], value: ms[2]}));
-    },
+    }
 
     _computeScoreClass(score, labelExtremes) {
       // Polymer 2: check for undefined
@@ -222,14 +231,14 @@
         }
       }
       return classes.join(' ');
-    },
+    }
 
     _computeClass(expanded, showAvatar, message) {
       const classes = [];
       classes.push(expanded ? 'expanded' : 'collapsed');
       classes.push(showAvatar ? 'showAvatar' : 'hideAvatar');
       return classes.join(' ');
-    },
+    }
 
     _handleAnchorClick(e) {
       e.preventDefault();
@@ -238,26 +247,28 @@
         composed: true,
         detail: {id: this.message.id},
       }));
-    },
+    }
 
     _handleReplyTap(e) {
       e.preventDefault();
       this.fire('reply', {message: this.message});
-    },
+    }
 
     _projectNameChanged(name) {
       this.$.restAPI.getProjectConfig(name).then(config => {
         this._projectConfig = config;
       });
-    },
+    }
 
     _computeExpandToggleIcon(expanded) {
       return expanded ? 'gr-icons:expand-less' : 'gr-icons:expand-more';
-    },
+    }
 
     _toggleExpanded(e) {
       e.stopPropagation();
       this.set('message.expanded', !this.message.expanded);
-    },
-  });
+    }
+  }
+
+  customElements.define(GrMessage.is, GrMessage);
 })();
diff --git a/polygerrit-ui/app/elements/change/gr-message/gr-message_test.html b/polygerrit-ui/app/elements/change/gr-message/gr-message_test.html
index ef5a756..01bc691 100644
--- a/polygerrit-ui/app/elements/change/gr-message/gr-message_test.html
+++ b/polygerrit-ui/app/elements/change/gr-message/gr-message_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-message</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>
diff --git a/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list.js b/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list.js
index d90132d..92e7cce 100644
--- a/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list.js
+++ b/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list.js
@@ -25,57 +25,61 @@
     SHOW_MORE: 'show-more-messages',
   };
 
-  Polymer({
-    is: 'gr-messages-list',
+  class GrMessagesList extends Polymer.GestureEventListeners(
+      Polymer.LegacyElementMixin(
+          Polymer.Element)) {
+    static get is() { return 'gr-messages-list'; }
 
-    properties: {
-      changeNum: Number,
-      messages: {
-        type: Array,
-        value() { return []; },
-      },
-      reviewerUpdates: {
-        type: Array,
-        value() { return []; },
-      },
-      changeComments: Object,
-      projectName: String,
-      showReplyButtons: {
-        type: Boolean,
-        value: false,
-      },
-      labels: Object,
+    static get properties() {
+      return {
+        changeNum: Number,
+        messages: {
+          type: Array,
+          value() { return []; },
+        },
+        reviewerUpdates: {
+          type: Array,
+          value() { return []; },
+        },
+        changeComments: Object,
+        projectName: String,
+        showReplyButtons: {
+          type: Boolean,
+          value: false,
+        },
+        labels: Object,
 
-      _expanded: {
-        type: Boolean,
-        value: false,
-        observer: '_expandedChanged',
-      },
-      _hideAutomated: {
-        type: Boolean,
-        value: false,
-      },
-      /**
+        _expanded: {
+          type: Boolean,
+          value: false,
+          observer: '_expandedChanged',
+        },
+        _hideAutomated: {
+          type: Boolean,
+          value: false,
+        },
+        /**
        * The messages after processing and including merged reviewer updates.
        */
-      _processedMessages: {
-        type: Array,
-        computed: '_computeItems(messages, reviewerUpdates)',
-        observer: '_processedMessagesChanged',
-      },
-      /**
+        _processedMessages: {
+          type: Array,
+          computed: '_computeItems(messages, reviewerUpdates)',
+          observer: '_processedMessagesChanged',
+        },
+        /**
        * The subset of _processedMessages that is visible to the user.
        */
-      _visibleMessages: {
-        type: Array,
-        value() { return []; },
-      },
+        _visibleMessages: {
+          type: Array,
+          value() { return []; },
+        },
 
-      _labelExtremes: {
-        type: Object,
-        computed: '_computeLabelExtremes(labels.*)',
-      },
-    },
+        _labelExtremes: {
+          type: Object,
+          computed: '_computeLabelExtremes(labels.*)',
+        },
+      };
+    }
 
     scrollToMessage(messageID) {
       let el = this.$$('[data-message-id="' + messageID + '"]');
@@ -108,12 +112,12 @@
       }
       window.scrollTo(0, top);
       this._highlightEl(el);
-    },
+    }
 
     _isAutomated(message) {
       return !!(message.reviewer ||
           (message.tag && message.tag.startsWith('autogenerated')));
-    },
+    }
 
     _computeItems(messages, reviewerUpdates) {
       // Polymer 2: check for undefined
@@ -152,7 +156,7 @@
         }
       }
       return result;
-    },
+    }
 
     _expandedChanged(exp) {
       if (this._processedMessages) {
@@ -169,7 +173,7 @@
           this.notifyPath(`_visibleMessages.${i}.expanded`);
         }
       }
-    },
+    }
 
     _highlightEl(el) {
       const highlightedEls =
@@ -183,23 +187,23 @@
       }
       el.addEventListener('animationend', handleAnimationEnd);
       el.classList.add('highlighted');
-    },
+    }
 
     /**
      * @param {boolean} expand
      */
     handleExpandCollapse(expand) {
       this._expanded = expand;
-    },
+    }
 
     _handleExpandCollapseTap(e) {
       e.preventDefault();
       this.handleExpandCollapse(!this._expanded);
-    },
+    }
 
     _handleAnchorClick(e) {
       this.scrollToMessage(e.detail.id);
-    },
+    }
 
     _hasAutomatedMessages(messages) {
       if (!messages) { return false; }
@@ -209,11 +213,11 @@
         }
       }
       return false;
-    },
+    }
 
     _computeExpandCollapseMessage(expanded) {
       return expanded ? 'Collapse all' : 'Expand all';
-    },
+    }
 
     /**
      * Computes message author's file comments for change's message.
@@ -268,7 +272,7 @@
         }
       }
       return msgComments;
-    },
+    }
 
     /**
      * Returns the number of messages to splice to the beginning of
@@ -293,7 +297,7 @@
         delta = msgsRemaining - i;
       }
       return Math.min(msgsRemaining, delta);
-    },
+    }
 
     /**
      * Gets the number of messages that would be visible, but do not currently
@@ -309,20 +313,20 @@
             this._getHumanMessages(visibleMessages).length;
       }
       return messages.length - visibleMessages.length;
-    },
+    }
 
     _computeIncrementText(visibleMessages, messages, hideAutomated) {
       let delta = this._getDelta(visibleMessages, messages, hideAutomated);
       delta = Math.min(
           this._numRemaining(visibleMessages, messages, hideAutomated), delta);
       return 'Show ' + Math.min(MESSAGES_INCREMENT, delta) + ' more';
-    },
+    }
 
     _getHumanMessages(messages) {
       return messages.filter(msg => {
         return !this._isAutomated(msg);
       });
-    },
+    }
 
     _computeShowHideTextHidden(visibleMessages, messages,
         hideAutomated) {
@@ -335,12 +339,12 @@
         visibleMessages = this._getHumanMessages(visibleMessages);
       }
       return visibleMessages.length >= messages.length;
-    },
+    }
 
     _handleShowAllTap() {
       this._visibleMessages = this._processedMessages;
       this.$.reporting.reportInteraction(ReportingEvent.SHOW_ALL);
-    },
+    }
 
     _handleIncrementShownMessages() {
       const delta = this._getDelta(this._visibleMessages,
@@ -350,27 +354,27 @@
       // Add newMessages to the beginning of _visibleMessages
       this.splice(...['_visibleMessages', 0, 0].concat(newMessages));
       this.$.reporting.reportInteraction(ReportingEvent.SHOW_MORE);
-    },
+    }
 
     _processedMessagesChanged(messages) {
       if (messages) {
         this._visibleMessages = messages.slice(-MAX_INITIAL_SHOWN_MESSAGES);
       }
-    },
+    }
 
     _computeNumMessagesText(visibleMessages, messages,
         hideAutomated) {
       const total =
           this._numRemaining(visibleMessages, messages, hideAutomated);
       return total === 1 ? 'Show 1 message' : 'Show all ' + total + ' messages';
-    },
+    }
 
     _computeIncrementHidden(visibleMessages, messages,
         hideAutomated) {
       const total =
           this._numRemaining(visibleMessages, messages, hideAutomated);
       return total <= this._getDelta(visibleMessages, messages, hideAutomated);
-    },
+    }
 
     /**
      * Compute a mapping from label name to objects representing the minimum and
@@ -389,6 +393,8 @@
         extremes[key] = {min: values[0], max: values[values.length - 1]};
       }
       return extremes;
-    },
-  });
+    }
+  }
+
+  customElements.define(GrMessagesList.is, GrMessagesList);
 })();
diff --git a/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list_test.html b/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list_test.html
index 265cee98..9caf13d 100644
--- a/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list_test.html
+++ b/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-messages-list</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>
diff --git a/polygerrit-ui/app/elements/change/gr-related-changes-list/gr-related-changes-list.js b/polygerrit-ui/app/elements/change/gr-related-changes-list/gr-related-changes-list.js
index 18a136e..25a56a9 100644
--- a/polygerrit-ui/app/elements/change/gr-related-changes-list/gr-related-changes-list.js
+++ b/polygerrit-ui/app/elements/change/gr-related-changes-list/gr-related-changes-list.js
@@ -17,9 +17,19 @@
 (function() {
   'use strict';
 
-  Polymer({
-    is: 'gr-related-changes-list',
-
+  /**
+    * @appliesMixin Gerrit.FireMixin
+    * @appliesMixin Gerrit.PatchSetMixin
+    * @appliesMixin Gerrit.RESTClientMixin
+    */
+  class GrRelatedChangesList extends Polymer.mixinBehaviors( [
+    Gerrit.FireBehavior,
+    Gerrit.PatchSetBehavior,
+    Gerrit.RESTClientBehavior,
+  ], Polymer.GestureEventListeners(
+      Polymer.LegacyElementMixin(
+          Polymer.Element))) {
+    static get is() { return 'gr-related-changes-list'; }
     /**
      * Fired when a new section is loaded so that the change view can determine
      * a show more button is needed, sometimes before all the sections finish
@@ -28,64 +38,62 @@
      * @event new-section-loaded
      */
 
-    properties: {
-      change: Object,
-      hasParent: {
-        type: Boolean,
-        notify: true,
-        value: false,
-      },
-      patchNum: String,
-      parentChange: Object,
-      hidden: {
-        type: Boolean,
-        value: false,
-        reflectToAttribute: true,
-      },
-      loading: {
-        type: Boolean,
-        notify: true,
-      },
-      mergeable: Boolean,
-      _connectedRevisions: {
-        type: Array,
-        computed: '_computeConnectedRevisions(change, patchNum, ' +
+    static get properties() {
+      return {
+        change: Object,
+        hasParent: {
+          type: Boolean,
+          notify: true,
+          value: false,
+        },
+        patchNum: String,
+        parentChange: Object,
+        hidden: {
+          type: Boolean,
+          value: false,
+          reflectToAttribute: true,
+        },
+        loading: {
+          type: Boolean,
+          notify: true,
+        },
+        mergeable: Boolean,
+        _connectedRevisions: {
+          type: Array,
+          computed: '_computeConnectedRevisions(change, patchNum, ' +
             '_relatedResponse.changes)',
-      },
-      /** @type {?} */
-      _relatedResponse: {
-        type: Object,
-        value() { return {changes: []}; },
-      },
-      /** @type {?} */
-      _submittedTogether: {
-        type: Object,
-        value() { return {changes: []}; },
-      },
-      _conflicts: {
-        type: Array,
-        value() { return []; },
-      },
-      _cherryPicks: {
-        type: Array,
-        value() { return []; },
-      },
-      _sameTopic: {
-        type: Array,
-        value() { return []; },
-      },
-    },
+        },
+        /** @type {?} */
+        _relatedResponse: {
+          type: Object,
+          value() { return {changes: []}; },
+        },
+        /** @type {?} */
+        _submittedTogether: {
+          type: Object,
+          value() { return {changes: []}; },
+        },
+        _conflicts: {
+          type: Array,
+          value() { return []; },
+        },
+        _cherryPicks: {
+          type: Array,
+          value() { return []; },
+        },
+        _sameTopic: {
+          type: Array,
+          value() { return []; },
+        },
+      };
+    }
 
-    behaviors: [
-      Gerrit.FireBehavior,
-      Gerrit.PatchSetBehavior,
-      Gerrit.RESTClientBehavior,
-    ],
-
-    observers: [
-      '_resultsChanged(_relatedResponse, _submittedTogether, ' +
+    static get observers() {
+      return [
+        '_resultsChanged(_relatedResponse, _submittedTogether, ' +
           '_conflicts, _cherryPicks, _sameTopic)',
-    ],
+      ];
+    }
 
     clear() {
       this.loading = true;
@@ -96,7 +104,7 @@
       this._conflicts = [];
       this._cherryPicks = [];
       this._sameTopic = [];
-    },
+    }
 
     reload() {
       if (!this.change || !this.patchNum) {
@@ -144,7 +152,7 @@
       return Promise.all(promises).then(() => {
         this.loading = false;
       });
-    },
+    }
 
     _fireReloadEvent() {
       // The listener on the change computes height of the related changes
@@ -152,7 +160,7 @@
       // that requires a flush.
       Polymer.dom.flush();
       this.dispatchEvent(new CustomEvent('new-section-loaded'));
-    },
+    }
 
     /**
      * Determines whether or not the given change has a parent change. If there
@@ -166,34 +174,34 @@
       return relatedChanges.length > 0 &&
           relatedChanges[relatedChanges.length - 1].change_id !==
           currentChangeId;
-    },
+    }
 
     _getRelatedChanges() {
       return this.$.restAPI.getRelatedChanges(this.change._number,
           this.patchNum);
-    },
+    }
 
     _getSubmittedTogether() {
       return this.$.restAPI.getChangesSubmittedTogether(this.change._number);
-    },
+    }
 
     _getServerConfig() {
       return this.$.restAPI.getConfig();
-    },
+    }
 
     _getConflicts() {
       return this.$.restAPI.getChangeConflicts(this.change._number);
-    },
+    }
 
     _getCherryPicks() {
       return this.$.restAPI.getChangeCherryPicks(this.change.project,
           this.change.change_id, this.change._number);
-    },
+    }
 
     _getChangesWithSameTopic() {
       return this.$.restAPI.getChangesWithSameTopic(this.change.topic,
           this.change._number);
-    },
+    }
 
     /**
      * @param {number} changeNum
@@ -203,7 +211,7 @@
      */
     _computeChangeURL(changeNum, project, opt_patchNum) {
       return Gerrit.Nav.getUrlForChangeById(changeNum, project, opt_patchNum);
-    },
+    }
 
     _computeChangeContainerClass(currentChange, relatedChange) {
       const classes = ['changeContainer'];
@@ -214,7 +222,7 @@
         classes.push('thisChange');
       }
       return classes.join(' ');
-    },
+    }
 
     /**
      * Do the given objects describe the same change? Compares the changes by
@@ -229,7 +237,7 @@
       const aNum = this._getChangeNumber(a);
       const bNum = this._getChangeNumber(b);
       return aNum === bNum;
-    },
+    }
 
     /**
      * Get the change number from either a ChangeInfo (such as those included in
@@ -251,7 +259,7 @@
         return change._change_number;
       }
       return change._number;
-    },
+    }
 
     _computeLinkClass(change) {
       const statuses = [];
@@ -262,7 +270,7 @@
         statuses.push('submittable');
       }
       return statuses.join(' ');
-    },
+    }
 
     _computeChangeStatusClass(change) {
       const classes = ['status'];
@@ -276,7 +284,7 @@
         classes.push('hidden');
       }
       return classes.join(' ');
-    },
+    }
 
     _computeChangeStatus(change) {
       switch (change.status) {
@@ -293,7 +301,7 @@
         return 'Submittable';
       }
       return '';
-    },
+    }
 
     _resultsChanged(related, submittedTogether, conflicts,
         cherryPicks, sameTopic) {
@@ -323,11 +331,11 @@
         }
       }
       this.hidden = true;
-    },
+    }
 
     _isIndirectAncestor(change) {
       return !this._connectedRevisions.includes(change.commit.commit);
-    },
+    }
 
     _computeConnectedRevisions(change, patchNum, relatedChanges) {
       // Polymer 2: check for undefined
@@ -364,7 +372,7 @@
         --pos;
       }
       return connected;
-    },
+    }
 
     _computeSubmittedTogetherClass(submittedTogether) {
       if (!submittedTogether || (
@@ -373,11 +381,13 @@
         return 'hidden';
       }
       return '';
-    },
+    }
 
     _computeNonVisibleChangesNote(n) {
       const noun = n === 1 ? 'change' : 'changes';
       return `(+ ${n} non-visible ${noun})`;
-    },
-  });
+    }
+  }
+
+  customElements.define(GrRelatedChangesList.is, GrRelatedChangesList);
 })();
diff --git a/polygerrit-ui/app/elements/change/gr-related-changes-list/gr-related-changes-list_test.html b/polygerrit-ui/app/elements/change/gr-related-changes-list/gr-related-changes-list_test.html
index f04d40e..38b2fb9 100644
--- a/polygerrit-ui/app/elements/change/gr-related-changes-list/gr-related-changes-list_test.html
+++ b/polygerrit-ui/app/elements/change/gr-related-changes-list/gr-related-changes-list_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-related-changes-list</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>
diff --git a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog-it_test.html b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog-it_test.html
index a65f76c..7d95323 100644
--- a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog-it_test.html
+++ b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog-it_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-reply-dialog</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>
diff --git a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.js b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.js
index b2b1588..eab1c8d 100644
--- a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.js
+++ b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.js
@@ -52,9 +52,23 @@
 
   const SEND_REPLY_TIMING_LABEL = 'SendReply';
 
-  Polymer({
-    is: 'gr-reply-dialog',
-
+  /**
+    * @appliesMixin Gerrit.BaseUrlMixin
+    * @appliesMixin Gerrit.FireMixin
+    * @appliesMixin Gerrit.KeyboardShortcutMixin
+    * @appliesMixin Gerrit.PatchSetMixin
+    * @appliesMixin Gerrit.RESTClientMixin
+    */
+  class GrReplyDialog extends Polymer.mixinBehaviors( [
+    Gerrit.BaseUrlBehavior,
+    Gerrit.FireBehavior,
+    Gerrit.KeyboardShortcutBehavior,
+    Gerrit.PatchSetBehavior,
+    Gerrit.RESTClientBehavior,
+  ], Polymer.GestureEventListeners(
+      Polymer.LegacyElementMixin(
+          Polymer.Element))) {
+    static get is() { return 'gr-reply-dialog'; }
     /**
      * Fired when a reply is successfully sent.
      *
@@ -93,156 +107,159 @@
       * @event send-disabled-changed
       */
 
-    properties: {
+    constructor() {
+      super();
+      this.FocusTarget = FocusTarget;
+    }
+
+    static get properties() {
+      return {
       /**
        * @type {{ _number: number, removable_reviewers: Array }}
        */
-      change: Object,
-      patchNum: String,
-      canBeStarted: {
-        type: Boolean,
-        value: false,
-      },
-      disabled: {
-        type: Boolean,
-        value: false,
-        reflectToAttribute: true,
-      },
-      draft: {
-        type: String,
-        value: '',
-        observer: '_draftChanged',
-      },
-      quote: {
-        type: String,
-        value: '',
-      },
-      diffDrafts: {
-        type: Object,
-        observer: '_handleHeightChanged',
-      },
-      /** @type {!Function} */
-      filterReviewerSuggestion: {
-        type: Function,
-        value() {
-          return this._filterReviewerSuggestionGenerator(false);
+        change: Object,
+        patchNum: String,
+        canBeStarted: {
+          type: Boolean,
+          value: false,
         },
-      },
-      /** @type {!Function} */
-      filterCCSuggestion: {
-        type: Function,
-        value() {
-          return this._filterReviewerSuggestionGenerator(true);
+        disabled: {
+          type: Boolean,
+          value: false,
+          reflectToAttribute: true,
         },
-      },
-      permittedLabels: Object,
-      /**
+        draft: {
+          type: String,
+          value: '',
+          observer: '_draftChanged',
+        },
+        quote: {
+          type: String,
+          value: '',
+        },
+        diffDrafts: {
+          type: Object,
+          observer: '_handleHeightChanged',
+        },
+        /** @type {!Function} */
+        filterReviewerSuggestion: {
+          type: Function,
+          value() {
+            return this._filterReviewerSuggestionGenerator(false);
+          },
+        },
+        /** @type {!Function} */
+        filterCCSuggestion: {
+          type: Function,
+          value() {
+            return this._filterReviewerSuggestionGenerator(true);
+          },
+        },
+        permittedLabels: Object,
+        /**
        * @type {{ commentlinks: Array }}
        */
-      projectConfig: Object,
-      knownLatestState: String,
-      underReview: {
-        type: Boolean,
-        value: true,
-      },
-
-      _account: Object,
-      _ccs: Array,
-      /** @type {?Object} */
-      _ccPendingConfirmation: {
-        type: Object,
-        observer: '_reviewerPendingConfirmationUpdated',
-      },
-      _messagePlaceholder: {
-        type: String,
-        computed: '_computeMessagePlaceholder(canBeStarted)',
-      },
-      _owner: Object,
-      /** @type {?} */
-      _pendingConfirmationDetails: Object,
-      _includeComments: {
-        type: Boolean,
-        value: true,
-      },
-      _reviewers: Array,
-      /** @type {?Object} */
-      _reviewerPendingConfirmation: {
-        type: Object,
-        observer: '_reviewerPendingConfirmationUpdated',
-      },
-      _previewFormatting: {
-        type: Boolean,
-        value: false,
-        observer: '_handleHeightChanged',
-      },
-      _reviewersPendingRemove: {
-        type: Object,
-        value: {
-          CC: [],
-          REVIEWER: [],
+        projectConfig: Object,
+        knownLatestState: String,
+        underReview: {
+          type: Boolean,
+          value: true,
         },
-      },
-      _sendButtonLabel: {
-        type: String,
-        computed: '_computeSendButtonLabel(canBeStarted)',
-      },
-      _savingComments: Boolean,
-      _reviewersMutated: {
-        type: Boolean,
-        value: false,
-      },
-      _labelsChanged: {
-        type: Boolean,
-        value: false,
-      },
-      _saveTooltip: {
-        type: String,
-        value: ButtonTooltips.SAVE,
-        readOnly: true,
-      },
-      _pluginMessage: {
-        type: String,
-        value: '',
-      },
-      _sendDisabled: {
-        type: Boolean,
-        computed: '_computeSendButtonDisabled(_sendButtonLabel, diffDrafts, ' +
-            'draft, _reviewersMutated, _labelsChanged, _includeComments, ' +
-            'disabled)',
-        observer: '_sendDisabledChanged',
-      },
-    },
 
-    FocusTarget,
+        _account: Object,
+        _ccs: Array,
+        /** @type {?Object} */
+        _ccPendingConfirmation: {
+          type: Object,
+          observer: '_reviewerPendingConfirmationUpdated',
+        },
+        _messagePlaceholder: {
+          type: String,
+          computed: '_computeMessagePlaceholder(canBeStarted)',
+        },
+        _owner: Object,
+        /** @type {?} */
+        _pendingConfirmationDetails: Object,
+        _includeComments: {
+          type: Boolean,
+          value: true,
+        },
+        _reviewers: Array,
+        /** @type {?Object} */
+        _reviewerPendingConfirmation: {
+          type: Object,
+          observer: '_reviewerPendingConfirmationUpdated',
+        },
+        _previewFormatting: {
+          type: Boolean,
+          value: false,
+          observer: '_handleHeightChanged',
+        },
+        _reviewersPendingRemove: {
+          type: Object,
+          value: {
+            CC: [],
+            REVIEWER: [],
+          },
+        },
+        _sendButtonLabel: {
+          type: String,
+          computed: '_computeSendButtonLabel(canBeStarted)',
+        },
+        _savingComments: Boolean,
+        _reviewersMutated: {
+          type: Boolean,
+          value: false,
+        },
+        _labelsChanged: {
+          type: Boolean,
+          value: false,
+        },
+        _saveTooltip: {
+          type: String,
+          value: ButtonTooltips.SAVE,
+          readOnly: true,
+        },
+        _pluginMessage: {
+          type: String,
+          value: '',
+        },
+        _sendDisabled: {
+          type: Boolean,
+          computed: '_computeSendButtonDisabled(_sendButtonLabel, ' +
+            'diffDrafts, draft, _reviewersMutated, _labelsChanged, ' +
+            '_includeComments, disabled)',
+          observer: '_sendDisabledChanged',
+        },
+      };
+    }
 
-    behaviors: [
-      Gerrit.BaseUrlBehavior,
-      Gerrit.FireBehavior,
-      Gerrit.KeyboardShortcutBehavior,
-      Gerrit.PatchSetBehavior,
-      Gerrit.RESTClientBehavior,
-    ],
+    get keyBindings() {
+      return {
+        'esc': '_handleEscKey',
+        'ctrl+enter meta+enter': '_handleEnterKey',
+      };
+    }
 
-    keyBindings: {
-      'esc': '_handleEscKey',
-      'ctrl+enter meta+enter': '_handleEnterKey',
-    },
-
-    observers: [
-      '_changeUpdated(change.reviewers.*, change.owner)',
-      '_ccsChanged(_ccs.splices)',
-      '_reviewersChanged(_reviewers.splices)',
-    ],
+    static get observers() {
+      return [
+        '_changeUpdated(change.reviewers.*, change.owner)',
+        '_ccsChanged(_ccs.splices)',
+        '_reviewersChanged(_reviewers.splices)',
+      ];
+    }
 
     attached() {
+      super.attached();
       this._getAccount().then(account => {
         this._account = account || {};
       });
-    },
+    }
 
     ready() {
+      super.ready();
       this.$.jsAPI.addElement(this.$.jsAPI.Element.REPLY_DIALOG, this);
-    },
+    }
 
     open(opt_focusTarget) {
       this.knownLatestState = LatestPatchState.CHECKING;
@@ -268,11 +285,11 @@
           this._savingComments = false;
         });
       }
-    },
+    }
 
     focus() {
       this._focusOn(FocusTarget.ANY);
-    },
+    }
 
     getFocusStops() {
       const end = this._sendDisabled ? this.$.cancelButton : this.$.sendButton;
@@ -280,14 +297,14 @@
         start: this.$.reviewers.focusStart,
         end,
       };
-    },
+    }
 
     setLabelValue(label, value) {
       const selectorEl =
           this.$.labelScores.$$(`gr-label-score-row[name="${label}"]`);
       if (!selectorEl) { return; }
       selectorEl.setSelectedValue(value);
-    },
+    }
 
     getLabelValue(label) {
       const selectorEl =
@@ -295,23 +312,23 @@
       if (!selectorEl) { return null; }
 
       return selectorEl.selectedValue;
-    },
+    }
 
     _handleEscKey(e) {
       this.cancel();
-    },
+    }
 
     _handleEnterKey(e) {
       this._submit();
-    },
+    }
 
     _ccsChanged(splices) {
       this._reviewerTypeChanged(splices, ReviewerTypes.CC);
-    },
+    }
 
     _reviewersChanged(splices) {
       this._reviewerTypeChanged(splices, ReviewerTypes.REVIEWER);
-    },
+    }
 
     _reviewerTypeChanged(splices, reviewerType) {
       if (splices && splices.indexSplices) {
@@ -342,7 +359,7 @@
           }
         }
       }
-    },
+    }
 
     _processReviewerChange(indexSplices, type) {
       for (const splice of indexSplices) {
@@ -354,7 +371,7 @@
           this._reviewersPendingRemove[type].push(account);
         }
       }
-    },
+    }
 
     /**
      * Resets the state of the _reviewersPendingRemove object, and removes
@@ -380,7 +397,7 @@
           this._reviewersPendingRemove[type] = [];
         }
       }
-    },
+    }
 
     /**
      * Removes an account from the change, both on the backend and the client.
@@ -404,7 +421,7 @@
           }
         }
       });
-    },
+    }
 
     _mapReviewer(reviewer) {
       let reviewerId;
@@ -416,7 +433,7 @@
         confirmed = reviewer.group.confirmed;
       }
       return {reviewer: reviewerId, confirmed};
-    },
+    }
 
     send(includeComments, startReview) {
       this.$.reporting.time(SEND_REPLY_TIMING_LABEL);
@@ -479,7 +496,7 @@
         this.disabled = false;
         throw err;
       });
-    },
+    }
 
     _focusOn(section) {
       // Safeguard- always want to focus on something.
@@ -497,7 +514,7 @@
         const ccEntry = this.$.ccs.focusStart;
         ccEntry.async(ccEntry.focus);
       }
-    },
+    }
 
     _chooseFocusTarget() {
       // If we are the owner and the reviewers field is empty, focus on that.
@@ -509,7 +526,7 @@
 
       // Default to BODY.
       return FocusTarget.BODY;
-    },
+    }
 
     _handle400Error(response) {
       // A call to _saveReview could fail with a server error if erroneous
@@ -551,11 +568,11 @@
         this.fire('server-error', {response});
         return null; // Means that the error has been handled.
       });
-    },
+    }
 
     _computeHideDraftList(drafts) {
       return Object.keys(drafts || {}).length == 0;
-    },
+    }
 
     _computeDraftsTitle(drafts) {
       let total = 0;
@@ -567,13 +584,13 @@
       if (total == 0) { return ''; }
       if (total == 1) { return '1 Draft'; }
       if (total > 1) { return total + ' Drafts'; }
-    },
+    }
 
     _computeMessagePlaceholder(canBeStarted) {
       return canBeStarted ?
         'Add a note for your reviewers...' :
         'Say something nice...';
-    },
+    }
 
     _changeUpdated(changeRecord, owner) {
       // Polymer 2: check for undefined
@@ -582,7 +599,7 @@
       }
 
       this._rebuildReviewerArrays(changeRecord.base, owner);
-    },
+    }
 
     _rebuildReviewerArrays(change, owner) {
       this._owner = owner;
@@ -614,11 +631,11 @@
 
       this._ccs = ccs;
       this._reviewers = reviewers;
-    },
+    }
 
     _accountOrGroupKey(entry) {
       return entry.id || entry._account_id;
-    },
+    }
 
     /**
      * Generates a function to filter out reviewer/CC entries. When isCCs is
@@ -650,23 +667,23 @@
         }
         return this._reviewers.find(finder) === undefined;
       };
-    },
+    }
 
     _getAccount() {
       return this.$.restAPI.getAccount();
-    },
+    }
 
     _cancelTapHandler(e) {
       e.preventDefault();
       this.cancel();
-    },
+    }
 
     cancel() {
       this.fire('cancel', null, {bubbles: false});
       this.$.textarea.closeDropdown();
       this._purgeReviewersPendingRemove(true);
       this._rebuildReviewerArrays(this.change.reviewers, this._owner);
-    },
+    }
 
     _saveTapHandler(e) {
       e.preventDefault();
@@ -678,12 +695,12 @@
       this.send(this._includeComments, false).then(keepReviewers => {
         this._purgeReviewersPendingRemove(false, keepReviewers);
       });
-    },
+    }
 
     _sendTapHandler(e) {
       e.preventDefault();
       this._submit();
-    },
+    }
 
     _submit() {
       if (!this.$.ccs.submitEntryText()) {
@@ -710,12 +727,12 @@
               detail: {message: `Error submitting review ${err}`},
             }));
           });
-    },
+    }
 
     _saveReview(review, opt_errFn) {
       return this.$.restAPI.saveChangeReview(this.change._number, this.patchNum,
           review, opt_errFn);
-    },
+    }
 
     _reviewerPendingConfirmationUpdated(reviewer) {
       if (reviewer === null) {
@@ -725,7 +742,7 @@
             this._ccPendingConfirmation || this._reviewerPendingConfirmation;
         this.$.reviewerConfirmationOverlay.open();
       }
-    },
+    }
 
     _confirmPendingReviewer() {
       if (this._ccPendingConfirmation) {
@@ -735,7 +752,7 @@
         this.$.reviewers.confirmGroup(this._reviewerPendingConfirmation.group);
         this._focusOn(FocusTarget.REVIEWERS);
       }
-    },
+    }
 
     _cancelPendingReviewer() {
       this._ccPendingConfirmation = null;
@@ -744,7 +761,7 @@
       const target =
           this._ccPendingConfirmation ? FocusTarget.CCS : FocusTarget.REVIEWERS;
       this._focusOn(target);
-    },
+    }
 
     _getStorageLocation() {
       // Tests trigger this method without setting change.
@@ -754,12 +771,12 @@
         patchNum: '@change',
         path: '@change',
       };
-    },
+    }
 
     _loadStoredDraft() {
       const draft = this.$.storage.getDraftComment(this._getStorageLocation());
       return draft ? draft.message : '';
-    },
+    }
 
     _handleAccountTextEntry() {
       // When either of the account entries has input added to the autocomplete,
@@ -767,7 +784,7 @@
       //
       // Note: if the text is removed, the save button will not get disabled.
       this._reviewersMutated = true;
-    },
+    }
 
     _draftChanged(newDraft, oldDraft) {
       this.debounce('store', () => {
@@ -780,37 +797,37 @@
               this.draft);
         }
       }, STORAGE_DEBOUNCE_INTERVAL_MS);
-    },
+    }
 
     _handleHeightChanged(e) {
       this.fire('autogrow');
-    },
+    }
 
     _handleLabelsChanged() {
       this._labelsChanged = Object.keys(
           this.$.labelScores.getLabelValues()).length !== 0;
-    },
+    }
 
     _isState(knownLatestState, value) {
       return knownLatestState === value;
-    },
+    }
 
     _reload() {
       // Load the current change without any patch range.
       location.href = this.getBaseUrl() + '/c/' + this.change._number;
-    },
+    }
 
     _computeSendButtonLabel(canBeStarted) {
       return canBeStarted ? ButtonLabels.START_REVIEW : ButtonLabels.SEND;
-    },
+    }
 
     _computeSendButtonTooltip(canBeStarted) {
       return canBeStarted ? ButtonTooltips.START_REVIEW : ButtonTooltips.SEND;
-    },
+    }
 
     _computeSavingLabelClass(savingComments) {
       return savingComments ? 'saving' : '';
-    },
+    }
 
     _computeSendButtonDisabled(buttonLabel, drafts, text, reviewersMutated,
         labelsChanged, includeComments, disabled) {
@@ -831,7 +848,7 @@
       if (buttonLabel === ButtonLabels.START_REVIEW) { return false; }
       const hasDrafts = includeComments && Object.keys(drafts).length;
       return !hasDrafts && !text.length && !reviewersMutated && !labelsChanged;
-    },
+    }
 
     _computePatchSetWarning(patchNum, labelsChanged) {
       let str = `Patch ${patchNum} is not latest.`;
@@ -839,28 +856,30 @@
         str += ' Voting on a non-latest patch will have no effect.';
       }
       return str;
-    },
+    }
 
     setPluginMessage(message) {
       this._pluginMessage = message;
-    },
+    }
 
     _sendDisabledChanged(sendDisabled) {
       this.dispatchEvent(new CustomEvent('send-disabled-changed'));
-    },
+    }
 
     _getReviewerSuggestionsProvider(change) {
       const provider = GrReviewerSuggestionsProvider.create(this.$.restAPI,
           change._number, Gerrit.SUGGESTIONS_PROVIDERS_USERS_TYPES.REVIEWER);
       provider.init();
       return provider;
-    },
+    }
 
     _getCcSuggestionsProvider(change) {
       const provider = GrReviewerSuggestionsProvider.create(this.$.restAPI,
           change._number, Gerrit.SUGGESTIONS_PROVIDERS_USERS_TYPES.CC);
       provider.init();
       return provider;
-    },
-  });
+    }
+  }
+
+  customElements.define(GrReplyDialog.is, GrReplyDialog);
 })();
diff --git a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog_test.html b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog_test.html
index d8d49cf..badd62a 100644
--- a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog_test.html
+++ b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-reply-dialog</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>
diff --git a/polygerrit-ui/app/elements/change/gr-reviewer-list/gr-reviewer-list.html b/polygerrit-ui/app/elements/change/gr-reviewer-list/gr-reviewer-list.html
index 8dbdde1..a5875ab1 100644
--- a/polygerrit-ui/app/elements/change/gr-reviewer-list/gr-reviewer-list.html
+++ b/polygerrit-ui/app/elements/change/gr-reviewer-list/gr-reviewer-list.html
@@ -32,51 +32,35 @@
         opacity: .8;
         pointer-events: none;
       }
-      .autocompleteContainer {
-        position: relative;
-      }
-      .hiddenReviewers {
-        margin-top: var(--spacing-s);
-      }
-      .inputContainer {
-        display: flex;
-        margin-top: var(--spacing-xs);
-      }
-      .inputContainer input {
-        flex: 1;
-      }
-      gr-account-chip {
+      .container > :not(:first-child) {
         margin-top: var(--spacing-s);
       }
       gr-button {
         --gr-button: {
-          padding: 5px 0px;
-        }
-      }
-      @media screen and (max-width: 50em), screen and (min-width: 75em) {
-        gr-account-chip:first-of-type {
-          margin-top: 0;
+          padding: 0px 0px;
         }
       }
     </style>
-    <template is="dom-repeat" items="[[_displayedReviewers]]" as="reviewer">
-      <gr-account-chip class="reviewer" account="[[reviewer]]"
-          on-remove="_handleRemove"
-          additional-text="[[_computeReviewerTooltip(reviewer, change)]]"
-          removable="[[_computeCanRemoveReviewer(reviewer, mutable)]]">
-      </gr-account-chip>
-    </template>
-    <gr-button
-        class="hiddenReviewers"
-        link
-        hidden$="[[!_hiddenReviewerCount]]"
-        on-click="_handleViewAll">and [[_hiddenReviewerCount]] more</gr-button>
-    <div class="controlsContainer" hidden$="[[!mutable]]">
+    <div class="container">
+      <template is="dom-repeat" items="[[_displayedReviewers]]" as="reviewer">
+        <gr-account-chip class="reviewer" account="[[reviewer]]"
+            on-remove="_handleRemove"
+            additional-text="[[_computeReviewerTooltip(reviewer, change)]]"
+            removable="[[_computeCanRemoveReviewer(reviewer, mutable)]]">
+        </gr-account-chip>
+      </template>
       <gr-button
+          class="hiddenReviewers"
           link
-          id="addReviewer"
-          class="addReviewer"
-          on-click="_handleAddTap">[[_addLabel]]</gr-button>
+          hidden$="[[!_hiddenReviewerCount]]"
+          on-click="_handleViewAll">and [[_hiddenReviewerCount]] more</gr-button>
+      <div class="controlsContainer" hidden$="[[!mutable]]">
+        <gr-button
+            link
+            id="addReviewer"
+            class="addReviewer"
+            on-click="_handleAddTap">[[_addLabel]]</gr-button>
+      </div>
     </div>
     <gr-rest-api-interface id="restAPI"></gr-rest-api-interface>
   </template>
diff --git a/polygerrit-ui/app/elements/change/gr-reviewer-list/gr-reviewer-list.js b/polygerrit-ui/app/elements/change/gr-reviewer-list/gr-reviewer-list.js
index 284f1d6..3cc79fa 100644
--- a/polygerrit-ui/app/elements/change/gr-reviewer-list/gr-reviewer-list.js
+++ b/polygerrit-ui/app/elements/change/gr-reviewer-list/gr-reviewer-list.js
@@ -17,69 +17,75 @@
 (function() {
   'use strict';
 
-  Polymer({
-    is: 'gr-reviewer-list',
-
+  /**
+    * @appliesMixin Gerrit.FireMixin
+    */
+  class GrReviewerList extends Polymer.mixinBehaviors( [
+    Gerrit.FireBehavior,
+  ], Polymer.GestureEventListeners(
+      Polymer.LegacyElementMixin(
+          Polymer.Element))) {
+    static get is() { return 'gr-reviewer-list'; }
     /**
      * Fired when the "Add reviewer..." button is tapped.
      *
      * @event show-reply-dialog
      */
 
-    properties: {
-      change: Object,
-      disabled: {
-        type: Boolean,
-        value: false,
-        reflectToAttribute: true,
-      },
-      mutable: {
-        type: Boolean,
-        value: false,
-      },
-      reviewersOnly: {
-        type: Boolean,
-        value: false,
-      },
-      ccsOnly: {
-        type: Boolean,
-        value: false,
-      },
-      maxReviewersDisplayed: Number,
+    static get properties() {
+      return {
+        change: Object,
+        disabled: {
+          type: Boolean,
+          value: false,
+          reflectToAttribute: true,
+        },
+        mutable: {
+          type: Boolean,
+          value: false,
+        },
+        reviewersOnly: {
+          type: Boolean,
+          value: false,
+        },
+        ccsOnly: {
+          type: Boolean,
+          value: false,
+        },
+        maxReviewersDisplayed: Number,
 
-      _displayedReviewers: {
-        type: Array,
-        value() { return []; },
-      },
-      _reviewers: {
-        type: Array,
-        value() { return []; },
-      },
-      _showInput: {
-        type: Boolean,
-        value: false,
-      },
-      _addLabel: {
-        type: String,
-        computed: '_computeAddLabel(ccsOnly)',
-      },
-      _hiddenReviewerCount: {
-        type: Number,
-        computed: '_computeHiddenCount(_reviewers, _displayedReviewers)',
-      },
+        _displayedReviewers: {
+          type: Array,
+          value() { return []; },
+        },
+        _reviewers: {
+          type: Array,
+          value() { return []; },
+        },
+        _showInput: {
+          type: Boolean,
+          value: false,
+        },
+        _addLabel: {
+          type: String,
+          computed: '_computeAddLabel(ccsOnly)',
+        },
+        _hiddenReviewerCount: {
+          type: Number,
+          computed: '_computeHiddenCount(_reviewers, _displayedReviewers)',
+        },
 
-      // Used for testing.
-      _lastAutocompleteRequest: Object,
-      _xhrPromise: Object,
-    },
+        // Used for testing.
+        _lastAutocompleteRequest: Object,
+        _xhrPromise: Object,
+      };
+    }
 
-    behaviors: [
-      Gerrit.FireBehavior,
-    ],
-
-    observers: [
-      '_reviewersChanged(change.reviewers.*, change.owner)',
-    ],
+    static get observers() {
+      return [
+        '_reviewersChanged(change.reviewers.*, change.owner)',
+      ];
+    }
 
     /**
      * Converts change.permitted_labels to an array of hashes of label keys to
@@ -100,7 +106,7 @@
         label,
         scores: labels[label].map(v => parseInt(v, 10)),
       }));
-    },
+    }
 
     /**
      * Returns hash of labels to max permitted score.
@@ -114,7 +120,7 @@
                 .map(v => parseInt(v, 10))
                 .reduce((a, b) => Math.max(a, b))}))
           .reduce((acc, i) => Object.assign(acc, i), {});
-    },
+    }
 
     /**
      * Returns max permitted score for reviewer.
@@ -139,7 +145,7 @@
         return 0;
       }
       return NaN;
-    },
+    }
 
     _computeReviewerTooltip(reviewer, change) {
       if (!change || !change.labels) { return ''; }
@@ -160,7 +166,7 @@
       } else {
         return '';
       }
-    },
+    }
 
     _reviewersChanged(changeRecord, owner) {
       // Polymer 2: check for undefined
@@ -194,7 +200,7 @@
       } else {
         this._displayedReviewers = this._reviewers;
       }
-    },
+    }
 
     _computeHiddenCount(reviewers, displayedReviewers) {
       // Polymer 2: check for undefined
@@ -203,7 +209,7 @@
       }
 
       return reviewers.length - displayedReviewers.length;
-    },
+    }
 
     _computeCanRemoveReviewer(reviewer, mutable) {
       if (!mutable) { return false; }
@@ -217,7 +223,7 @@
         }
       }
       return false;
-    },
+    }
 
     _handleRemove(e) {
       e.preventDefault();
@@ -245,7 +251,7 @@
         this.disabled = false;
         throw err;
       });
-    },
+    }
 
     _handleAddTap(e) {
       e.preventDefault();
@@ -257,18 +263,20 @@
         value.ccsOnly = true;
       }
       this.fire('show-reply-dialog', {value});
-    },
+    }
 
     _handleViewAll(e) {
       this._displayedReviewers = this._reviewers;
-    },
+    }
 
     _removeReviewer(id) {
       return this.$.restAPI.removeChangeReviewer(this.change._number, id);
-    },
+    }
 
     _computeAddLabel(ccsOnly) {
       return ccsOnly ? 'Add CC' : 'Add reviewer';
-    },
-  });
+    }
+  }
+
+  customElements.define(GrReviewerList.is, GrReviewerList);
 })();
diff --git a/polygerrit-ui/app/elements/change/gr-reviewer-list/gr-reviewer-list_test.html b/polygerrit-ui/app/elements/change/gr-reviewer-list/gr-reviewer-list_test.html
index e870993..18f7bc5 100644
--- a/polygerrit-ui/app/elements/change/gr-reviewer-list/gr-reviewer-list_test.html
+++ b/polygerrit-ui/app/elements/change/gr-reviewer-list/gr-reviewer-list_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-reviewer-list</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>
diff --git a/polygerrit-ui/app/elements/change/gr-thread-list/gr-thread-list.js b/polygerrit-ui/app/elements/change/gr-thread-list/gr-thread-list.js
index 518a013..3a6b2df 100644
--- a/polygerrit-ui/app/elements/change/gr-thread-list/gr-thread-list.js
+++ b/polygerrit-ui/app/elements/change/gr-thread-list/gr-thread-list.js
@@ -22,39 +22,42 @@
    *
    * @event thread-list-modified
    */
+  class GrThreadList extends Polymer.GestureEventListeners(
+      Polymer.LegacyElementMixin(
+          Polymer.Element)) {
+    static get is() { return 'gr-thread-list'; }
 
-  Polymer({
-    is: 'gr-thread-list',
-
-    properties: {
+    static get properties() {
+      return {
       /** @type {?} */
-      change: Object,
-      threads: Array,
-      changeNum: String,
-      loggedIn: Boolean,
-      _sortedThreads: {
-        type: Array,
-      },
-      _filteredThreads: {
-        type: Array,
-        computed: '_computeFilteredThreads(_sortedThreads, _unresolvedOnly, ' +
-            '_draftsOnly)',
-      },
-      _unresolvedOnly: {
-        type: Boolean,
-        value: false,
-      },
-      _draftsOnly: {
-        type: Boolean,
-        value: false,
-      },
-    },
+        change: Object,
+        threads: Array,
+        changeNum: String,
+        loggedIn: Boolean,
+        _sortedThreads: {
+          type: Array,
+        },
+        _filteredThreads: {
+          type: Array,
+          computed: '_computeFilteredThreads(_sortedThreads, ' +
+            '_unresolvedOnly, _draftsOnly)',
+        },
+        _unresolvedOnly: {
+          type: Boolean,
+          value: false,
+        },
+        _draftsOnly: {
+          type: Boolean,
+          value: false,
+        },
+      };
+    }
 
-    observers: ['_computeSortedThreads(threads.*)'],
+    static get observers() { return ['_computeSortedThreads(threads.*)']; }
 
     _computeShowDraftToggle(loggedIn) {
       return loggedIn ? 'show' : '';
-    },
+    }
 
     /**
      * Order as follows:
@@ -68,7 +71,7 @@
       const threads = changeRecord.base;
       if (!threads) { return []; }
       this._updateSortedThreads(threads);
-    },
+    }
 
     _updateSortedThreads(threads) {
       this._sortedThreads =
@@ -90,7 +93,7 @@
             }
             return dateCompare ? dateCompare : c1.id.localeCompare(c2.id);
           });
-    },
+    }
 
     _computeFilteredThreads(sortedThreads, unresolvedOnly, draftsOnly) {
       // Polymer 2: check for undefined
@@ -125,7 +128,7 @@
           return c;
         }
       }).map(threadInfo => threadInfo.thread);
-    },
+    }
 
     _getThreadWithSortInfo(thread) {
       const lastComment = thread.comments[thread.comments.length - 1] || {};
@@ -143,7 +146,7 @@
         hasDraft: !!lastComment.__draft,
         updated: lastComment.updated,
       };
-    },
+    }
 
     removeThread(rootId) {
       for (let i = 0; i < this.threads.length; i++) {
@@ -154,11 +157,11 @@
           return;
         }
       }
-    },
+    }
 
     _handleThreadDiscard(e) {
       this.removeThread(e.detail.rootId);
-    },
+    }
 
     _handleCommentsChanged(e) {
       // Reset threads so thread computations occur on deep array changes to
@@ -167,10 +170,12 @@
 
       this.dispatchEvent(new CustomEvent('thread-list-modified',
           {detail: {rootId: e.detail.rootId, path: e.detail.path}}));
-    },
+    }
 
     _isOnParent(side) {
       return !!side;
-    },
-  });
+    }
+  }
+
+  customElements.define(GrThreadList.is, GrThreadList);
 })();
diff --git a/polygerrit-ui/app/elements/change/gr-thread-list/gr-thread-list_test.html b/polygerrit-ui/app/elements/change/gr-thread-list/gr-thread-list_test.html
index e736651..b29246d 100644
--- a/polygerrit-ui/app/elements/change/gr-thread-list/gr-thread-list_test.html
+++ b/polygerrit-ui/app/elements/change/gr-thread-list/gr-thread-list_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-thread-list</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>
diff --git a/polygerrit-ui/app/elements/change/gr-upload-help-dialog/gr-upload-help-dialog.js b/polygerrit-ui/app/elements/change/gr-upload-help-dialog/gr-upload-help-dialog.js
index 092204a..a3ae1f9 100644
--- a/polygerrit-ui/app/elements/change/gr-upload-help-dialog/gr-upload-help-dialog.js
+++ b/polygerrit-ui/app/elements/change/gr-upload-help-dialog/gr-upload-help-dialog.js
@@ -27,41 +27,46 @@
     'pull',
   ];
 
-  Polymer({
-    is: 'gr-upload-help-dialog',
-
+  /**
+    * @appliesMixin Gerrit.FireMixin
+    */
+  class GrUploadHelpDialog extends Polymer.mixinBehaviors( [
+    Gerrit.FireBehavior,
+  ], Polymer.GestureEventListeners(
+      Polymer.LegacyElementMixin(
+          Polymer.Element))) {
+    static get is() { return 'gr-upload-help-dialog'; }
     /**
      * Fired when the user presses the close button.
      *
      * @event close
      */
 
-    properties: {
-      revision: Object,
-      targetBranch: String,
-      _commitCommand: {
-        type: String,
-        value: COMMIT_COMMAND,
-        readOnly: true,
-      },
-      _fetchCommand: {
-        type: String,
-        computed: '_computeFetchCommand(revision, ' +
+    static get properties() {
+      return {
+        revision: Object,
+        targetBranch: String,
+        _commitCommand: {
+          type: String,
+          value: COMMIT_COMMAND,
+          readOnly: true,
+        },
+        _fetchCommand: {
+          type: String,
+          computed: '_computeFetchCommand(revision, ' +
             '_preferredDownloadCommand, _preferredDownloadScheme)',
-      },
-      _preferredDownloadCommand: String,
-      _preferredDownloadScheme: String,
-      _pushCommand: {
-        type: String,
-        computed: '_computePushCommand(targetBranch)',
-      },
-    },
-
-    behaviors: [
-      Gerrit.FireBehavior,
-    ],
+        },
+        _preferredDownloadCommand: String,
+        _preferredDownloadScheme: String,
+        _pushCommand: {
+          type: String,
+          computed: '_computePushCommand(targetBranch)',
+        },
+      };
+    }
 
     attached() {
+      super.attached();
       this.$.restAPI.getLoggedIn().then(loggedIn => {
         if (loggedIn) {
           return this.$.restAPI.getPreferences();
@@ -72,13 +77,13 @@
           this._preferredDownloadScheme = prefs.download_scheme;
         }
       });
-    },
+    }
 
     _handleCloseTap(e) {
       e.preventDefault();
       e.stopPropagation();
       this.fire('close', null, {bubbles: false});
-    },
+    }
 
     _computeFetchCommand(revision, preferredDownloadCommand,
         preferredDownloadScheme) {
@@ -126,10 +131,12 @@
       }
 
       return undefined;
-    },
+    }
 
     _computePushCommand(targetBranch) {
       return PUSH_COMMAND_PREFIX + targetBranch;
-    },
-  });
+    }
+  }
+
+  customElements.define(GrUploadHelpDialog.is, GrUploadHelpDialog);
 })();
diff --git a/polygerrit-ui/app/elements/change/gr-upload-help-dialog/gr-upload-help-dialog_test.html b/polygerrit-ui/app/elements/change/gr-upload-help-dialog/gr-upload-help-dialog_test.html
index 577b978..76377fb 100644
--- a/polygerrit-ui/app/elements/change/gr-upload-help-dialog/gr-upload-help-dialog_test.html
+++ b/polygerrit-ui/app/elements/change/gr-upload-help-dialog/gr-upload-help-dialog_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-upload-help-dialog</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>
diff --git a/polygerrit-ui/app/elements/core/gr-account-dropdown/gr-account-dropdown.js b/polygerrit-ui/app/elements/core/gr-account-dropdown/gr-account-dropdown.js
index 7cbe988..4f51a02 100644
--- a/polygerrit-ui/app/elements/core/gr-account-dropdown/gr-account-dropdown.js
+++ b/polygerrit-ui/app/elements/core/gr-account-dropdown/gr-account-dropdown.js
@@ -19,29 +19,39 @@
 
   const INTERPOLATE_URL_PATTERN = /\$\{([\w]+)\}/g;
 
-  Polymer({
-    is: 'gr-account-dropdown',
+  /**
+    * @appliesMixin Gerrit.DisplayNameMixin
+    */
+  class GrAccountDropdown extends Polymer.mixinBehaviors( [
+    Gerrit.DisplayNameBehavior,
+  ], Polymer.GestureEventListeners(
+      Polymer.LegacyElementMixin(
+          Polymer.Element))) {
+    static get is() { return 'gr-account-dropdown'; }
 
-    properties: {
-      account: Object,
-      config: Object,
-      links: {
-        type: Array,
-        computed: '_getLinks(_switchAccountUrl, _path)',
-      },
-      topContent: {
-        type: Array,
-        computed: '_getTopContent(account)',
-      },
-      _path: {
-        type: String,
-        value: '/',
-      },
-      _hasAvatars: Boolean,
-      _switchAccountUrl: String,
-    },
+    static get properties() {
+      return {
+        account: Object,
+        config: Object,
+        links: {
+          type: Array,
+          computed: '_getLinks(_switchAccountUrl, _path)',
+        },
+        topContent: {
+          type: Array,
+          computed: '_getTopContent(account)',
+        },
+        _path: {
+          type: String,
+          value: '/',
+        },
+        _hasAvatars: Boolean,
+        _switchAccountUrl: String,
+      };
+    }
 
     attached() {
+      super.attached();
       this._handleLocationChange();
       this.listen(window, 'location-change', '_handleLocationChange');
       this.$.restAPI.getConfig().then(cfg => {
@@ -54,15 +64,12 @@
         }
         this._hasAvatars = !!(cfg && cfg.plugin && cfg.plugin.has_avatars);
       });
-    },
-
-    behaviors: [
-      Gerrit.DisplayNameBehavior,
-    ],
+    }
 
     detached() {
+      super.detached();
       this.unlisten(window, 'location-change', '_handleLocationChange');
-    },
+    }
 
     _getLinks(switchAccountUrl, path) {
       // Polymer 2: check for undefined
@@ -78,30 +85,32 @@
       }
       links.push({name: 'Sign out', url: '/logout'});
       return links;
-    },
+    }
 
     _getTopContent(account) {
       return [
         {text: this._accountName(account), bold: true},
         {text: account.email ? account.email : ''},
       ];
-    },
+    }
 
     _handleLocationChange() {
       this._path =
           window.location.pathname +
           window.location.search +
           window.location.hash;
-    },
+    }
 
     _interpolateUrl(url, replacements) {
       return url.replace(INTERPOLATE_URL_PATTERN, (match, p1) => {
         return replacements[p1] || '';
       });
-    },
+    }
 
     _accountName(account) {
       return this.getUserName(this.config, account, true);
-    },
-  });
+    }
+  }
+
+  customElements.define(GrAccountDropdown.is, GrAccountDropdown);
 })();
diff --git a/polygerrit-ui/app/elements/core/gr-account-dropdown/gr-account-dropdown_test.html b/polygerrit-ui/app/elements/core/gr-account-dropdown/gr-account-dropdown_test.html
index e29faa8..9dafb74 100644
--- a/polygerrit-ui/app/elements/core/gr-account-dropdown/gr-account-dropdown_test.html
+++ b/polygerrit-ui/app/elements/core/gr-account-dropdown/gr-account-dropdown_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-account-dropdown</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>
diff --git a/polygerrit-ui/app/elements/core/gr-error-dialog/gr-error-dialog.js b/polygerrit-ui/app/elements/core/gr-error-dialog/gr-error-dialog.js
index 8d3b58e..2655140 100644
--- a/polygerrit-ui/app/elements/core/gr-error-dialog/gr-error-dialog.js
+++ b/polygerrit-ui/app/elements/core/gr-error-dialog/gr-error-dialog.js
@@ -17,21 +17,26 @@
 (function() {
   'use strict';
 
-  Polymer({
-    is: 'gr-error-dialog',
-
+  class GrErrorDialog extends Polymer.GestureEventListeners(
+      Polymer.LegacyElementMixin(
+          Polymer.Element)) {
+    static get is() { return 'gr-error-dialog'; }
     /**
      * Fired when the dismiss button is pressed.
      *
      * @event dismiss
      */
 
-    properties: {
-      text: String,
-    },
+    static get properties() {
+      return {
+        text: String,
+      };
+    }
 
     _handleConfirm() {
       this.dispatchEvent(new CustomEvent('dismiss'));
-    },
-  });
+    }
+  }
+
+  customElements.define(GrErrorDialog.is, GrErrorDialog);
 })();
diff --git a/polygerrit-ui/app/elements/core/gr-error-dialog/gr-error-dialog_test.html b/polygerrit-ui/app/elements/core/gr-error-dialog/gr-error-dialog_test.html
index 648f8be..dd4a71a 100644
--- a/polygerrit-ui/app/elements/core/gr-error-dialog/gr-error-dialog_test.html
+++ b/polygerrit-ui/app/elements/core/gr-error-dialog/gr-error-dialog_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-error-dialog</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>
diff --git a/polygerrit-ui/app/elements/core/gr-error-manager/gr-error-manager.js b/polygerrit-ui/app/elements/core/gr-error-manager/gr-error-manager.js
index 5865e3c..7437187 100644
--- a/polygerrit-ui/app/elements/core/gr-error-manager/gr-error-manager.js
+++ b/polygerrit-ui/app/elements/core/gr-error-manager/gr-error-manager.js
@@ -25,40 +25,47 @@
   const TOO_MANY_FILES = 'too many files to find conflicts';
   const AUTHENTICATION_REQUIRED = 'Authentication required\n';
 
-  Polymer({
-    is: 'gr-error-manager',
+  /**
+    * @appliesMixin Gerrit.BaseUrlMixin
+    * @appliesMixin Gerrit.FireMixin
+    */
+  class GrErrorManager extends Polymer.mixinBehaviors( [
+    Gerrit.BaseUrlBehavior,
+    Gerrit.FireBehavior,
+  ], Polymer.GestureEventListeners(
+      Polymer.LegacyElementMixin(
+          Polymer.Element))) {
+    static get is() { return 'gr-error-manager'; }
 
-    behaviors: [
-      Gerrit.BaseUrlBehavior,
-      Gerrit.FireBehavior,
-    ],
-
-    properties: {
+    static get properties() {
+      return {
       /**
        * The ID of the account that was logged in when the app was launched. If
        * not set, then there was no account at launch.
        */
-      knownAccountId: Number,
+        knownAccountId: Number,
 
-      /** @type {?Object} */
-      _alertElement: Object,
-      /** @type {?number} */
-      _hideAlertHandle: Number,
-      _refreshingCredentials: {
-        type: Boolean,
-        value: false,
-      },
+        /** @type {?Object} */
+        _alertElement: Object,
+        /** @type {?number} */
+        _hideAlertHandle: Number,
+        _refreshingCredentials: {
+          type: Boolean,
+          value: false,
+        },
 
-      /**
+        /**
        * The time (in milliseconds) since the most recent credential check.
        */
-      _lastCredentialCheck: {
-        type: Number,
-        value() { return Date.now(); },
-      },
-    },
+        _lastCredentialCheck: {
+          type: Number,
+          value() { return Date.now(); },
+        },
+      };
+    }
 
     attached() {
+      super.attached();
       this.listen(document, 'server-error', '_handleServerError');
       this.listen(document, 'network-error', '_handleNetworkError');
       this.listen(document, 'auth-error', '_handleAuthError');
@@ -66,9 +73,10 @@
       this.listen(document, 'show-error', '_handleShowErrorDialog');
       this.listen(document, 'visibilitychange', '_handleVisibilityChange');
       this.listen(document, 'show-auth-required', '_handleAuthRequired');
-    },
+    }
 
     detached() {
+      super.detached();
       this._clearHideAlertHandle();
       this.unlisten(document, 'server-error', '_handleServerError');
       this.unlisten(document, 'network-error', '_handleNetworkError');
@@ -76,20 +84,20 @@
       this.unlisten(document, 'show-auth-required', '_handleAuthRequired');
       this.unlisten(document, 'visibilitychange', '_handleVisibilityChange');
       this.unlisten(document, 'show-error', '_handleShowErrorDialog');
-    },
+    }
 
     _shouldSuppressError(msg) {
       return msg.includes(TOO_MANY_FILES);
-    },
+    }
 
     _handleAuthRequired() {
       this._showAuthErrorAlert(
           'Log in is required to perform that action.', 'Log in.');
-    },
+    }
 
     _handleAuthError() {
       this._showAuthErrorAlert('Auth error', 'Refresh credentials.');
-    },
+    }
 
     _handleServerError(e) {
       const {request, response} = e.detail;
@@ -113,7 +121,7 @@
             }
             console.error(errorText);
           });
-    },
+    }
 
     _constructServerErrorMsg({errorText, status, statusText, url}) {
       let err = `Error ${status}`;
@@ -122,21 +130,21 @@
       if (errorText) { err += errorText; }
       if (url) { err += `\nEndpoint: ${url}`; }
       return err;
-    },
+    }
 
     _handleShowAlert(e) {
       this._showAlert(e.detail.message, e.detail.action, e.detail.callback,
           e.detail.dismissOnNavigation);
-    },
+    }
 
     _handleNetworkError(e) {
       this._showAlert('Server unavailable');
       console.error(e.detail.error.message);
-    },
+    }
 
     _getLoggedIn() {
       return this.$.restAPI.getLoggedIn();
-    },
+    }
 
     /**
      * @param {string} text
@@ -161,7 +169,7 @@
       const el = this._createToastAlert();
       el.show(text, opt_actionText, opt_actionCallback);
       this._alertElement = el;
-    },
+    }
 
     _hideAlert() {
       if (!this._alertElement) { return; }
@@ -171,14 +179,14 @@
 
       // Remove listener for page navigation, if it exists.
       this.unlisten(document, 'location-change', '_hideAlert');
-    },
+    }
 
     _clearHideAlertHandle() {
       if (this._hideAlertHandle != null) {
         this.cancelAsync(this._hideAlertHandle);
         this._hideAlertHandle = null;
       }
-    },
+    }
 
     _showAuthErrorAlert(errorText, actionText) {
       // TODO(viktard): close alert if it's not for auth error.
@@ -193,13 +201,13 @@
       if (!document.hidden) {
         this._handleVisibilityChange();
       }
-    },
+    }
 
     _createToastAlert() {
       const el = document.createElement('gr-alert');
       el.toast = true;
       return el;
-    },
+    }
 
     _handleVisibilityChange() {
       // Ignore when the page is transitioning to hidden (or hidden is
@@ -216,12 +224,12 @@
         this._lastCredentialCheck = Date.now();
         this.$.restAPI.checkCredentials();
       }
-    },
+    }
 
     _requestCheckLoggedIn() {
       this.debounce(
           'checkLoggedIn', this._checkSignedIn, CHECK_SIGN_IN_INTERVAL_MS);
-    },
+    }
 
     _checkSignedIn() {
       this.$.restAPI.checkCredentials().then(account => {
@@ -242,11 +250,11 @@
           }
         }
       });
-    },
+    }
 
     _reloadPage() {
       window.location.reload();
-    },
+    }
 
     _createLoginPopup() {
       const left = window.screenLeft +
@@ -262,31 +270,33 @@
       window.open(this.getBaseUrl() +
           '/login/%3FcloseAfterLogin', '_blank', options.join(','));
       this.listen(window, 'focus', '_handleWindowFocus');
-    },
+    }
 
     _handleCredentialRefreshed() {
       this.unlisten(window, 'focus', '_handleWindowFocus');
       this._refreshingCredentials = false;
       this._hideAlert();
       this._showAlert('Credentials refreshed.');
-    },
+    }
 
     _handleWindowFocus() {
       this.flushDebouncer('checkLoggedIn');
-    },
+    }
 
     _handleShowErrorDialog(e) {
       this._showErrorDialog(e.detail.message);
-    },
+    }
 
     _handleDismissErrorDialog() {
       this.$.errorOverlay.close();
-    },
+    }
 
     _showErrorDialog(message) {
       this.$.reporting.reportErrorDialog(message);
       this.$.errorDialog.text = message;
       this.$.errorOverlay.open();
-    },
-  });
+    }
+  }
+
+  customElements.define(GrErrorManager.is, GrErrorManager);
 })();
diff --git a/polygerrit-ui/app/elements/core/gr-error-manager/gr-error-manager_test.html b/polygerrit-ui/app/elements/core/gr-error-manager/gr-error-manager_test.html
index 9140c17..84b6717 100644
--- a/polygerrit-ui/app/elements/core/gr-error-manager/gr-error-manager_test.html
+++ b/polygerrit-ui/app/elements/core/gr-error-manager/gr-error-manager_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-error-manager</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>
diff --git a/polygerrit-ui/app/elements/core/gr-key-binding-display/gr-key-binding-display.js b/polygerrit-ui/app/elements/core/gr-key-binding-display/gr-key-binding-display.js
index 89d1091..2b86170 100644
--- a/polygerrit-ui/app/elements/core/gr-key-binding-display/gr-key-binding-display.js
+++ b/polygerrit-ui/app/elements/core/gr-key-binding-display/gr-key-binding-display.js
@@ -17,20 +17,26 @@
 (function() {
   'use strict';
 
-  Polymer({
-    is: 'gr-key-binding-display',
+  class GrKeyBindingDisplay extends Polymer.GestureEventListeners(
+      Polymer.LegacyElementMixin(
+          Polymer.Element)) {
+    static get is() { return 'gr-key-binding-display'; }
 
-    properties: {
+    static get properties() {
+      return {
       /** @type {Array<string>} */
-      binding: Array,
-    },
+        binding: Array,
+      };
+    }
 
     _computeModifiers(binding) {
       return binding.slice(0, binding.length - 1);
-    },
+    }
 
     _computeKey(binding) {
       return binding[binding.length - 1];
-    },
-  });
+    }
+  }
+
+  customElements.define(GrKeyBindingDisplay.is, GrKeyBindingDisplay);
 })();
diff --git a/polygerrit-ui/app/elements/core/gr-key-binding-display/gr-key-binding-display_test.html b/polygerrit-ui/app/elements/core/gr-key-binding-display/gr-key-binding-display_test.html
index 39c8af8..e0d2d40 100644
--- a/polygerrit-ui/app/elements/core/gr-key-binding-display/gr-key-binding-display_test.html
+++ b/polygerrit-ui/app/elements/core/gr-key-binding-display/gr-key-binding-display_test.html
@@ -17,7 +17,7 @@
 -->
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-key-binding-display</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>
diff --git a/polygerrit-ui/app/elements/core/gr-keyboard-shortcuts-dialog/gr-keyboard-shortcuts-dialog.js b/polygerrit-ui/app/elements/core/gr-keyboard-shortcuts-dialog/gr-keyboard-shortcuts-dialog.js
index 4bc6e11..e16c975 100644
--- a/polygerrit-ui/app/elements/core/gr-keyboard-shortcuts-dialog/gr-keyboard-shortcuts-dialog.js
+++ b/polygerrit-ui/app/elements/core/gr-keyboard-shortcuts-dialog/gr-keyboard-shortcuts-dialog.js
@@ -19,60 +19,68 @@
 
   const {ShortcutSection} = window.Gerrit.KeyboardShortcutBinder;
 
-  Polymer({
-    is: 'gr-keyboard-shortcuts-dialog',
-
+  /**
+    * @appliesMixin Gerrit.FireMixin
+    * @appliesMixin Gerrit.KeyboardShortcutMixin
+    */
+  class GrKeyboardShortcutsDialog extends Polymer.mixinBehaviors( [
+    Gerrit.FireBehavior,
+    Gerrit.KeyboardShortcutBehavior,
+  ], Polymer.GestureEventListeners(
+      Polymer.LegacyElementMixin(
+          Polymer.Element))) {
+    static get is() { return 'gr-keyboard-shortcuts-dialog'; }
     /**
      * Fired when the user presses the close button.
      *
      * @event close
      */
 
-    properties: {
-      _left: Array,
-      _right: Array,
+    static get properties() {
+      return {
+        _left: Array,
+        _right: Array,
 
-      _propertyBySection: {
-        type: Object,
-        value() {
-          return {
-            [ShortcutSection.EVERYWHERE]: '_everywhere',
-            [ShortcutSection.NAVIGATION]: '_navigation',
-            [ShortcutSection.DASHBOARD]: '_dashboard',
-            [ShortcutSection.CHANGE_LIST]: '_changeList',
-            [ShortcutSection.ACTIONS]: '_actions',
-            [ShortcutSection.REPLY_DIALOG]: '_replyDialog',
-            [ShortcutSection.FILE_LIST]: '_fileList',
-            [ShortcutSection.DIFFS]: '_diffs',
-          };
+        _propertyBySection: {
+          type: Object,
+          value() {
+            return {
+              [ShortcutSection.EVERYWHERE]: '_everywhere',
+              [ShortcutSection.NAVIGATION]: '_navigation',
+              [ShortcutSection.DASHBOARD]: '_dashboard',
+              [ShortcutSection.CHANGE_LIST]: '_changeList',
+              [ShortcutSection.ACTIONS]: '_actions',
+              [ShortcutSection.REPLY_DIALOG]: '_replyDialog',
+              [ShortcutSection.FILE_LIST]: '_fileList',
+              [ShortcutSection.DIFFS]: '_diffs',
+            };
+          },
         },
-      },
-    },
+      };
+    }
 
-    behaviors: [
-      Gerrit.FireBehavior,
-      Gerrit.KeyboardShortcutBehavior,
-    ],
-
-    hostAttributes: {
-      role: 'dialog',
-    },
+    ready() {
+      super.ready();
+      this._ensureAttribute('role', 'dialog');
+    }
 
     attached() {
+      super.attached();
       this.addKeyboardShortcutDirectoryListener(
           this._onDirectoryUpdated.bind(this));
-    },
+    }
 
     detached() {
+      super.detached();
       this.removeKeyboardShortcutDirectoryListener(
           this._onDirectoryUpdated.bind(this));
-    },
+    }
 
     _handleCloseTap(e) {
       e.preventDefault();
       e.stopPropagation();
       this.fire('close', null, {bubbles: false});
-    },
+    }
 
     _onDirectoryUpdated(directory) {
       const left = [];
@@ -122,6 +130,9 @@
 
       this.set('_left', left);
       this.set('_right', right);
-    },
-  });
+    }
+  }
+
+  customElements.define(GrKeyboardShortcutsDialog.is,
+      GrKeyboardShortcutsDialog);
 })();
diff --git a/polygerrit-ui/app/elements/core/gr-keyboard-shortcuts-dialog/gr-keyboard-shortcuts-dialog_test.html b/polygerrit-ui/app/elements/core/gr-keyboard-shortcuts-dialog/gr-keyboard-shortcuts-dialog_test.html
index 1a3d6c7..b713aa1 100644
--- a/polygerrit-ui/app/elements/core/gr-keyboard-shortcuts-dialog/gr-keyboard-shortcuts-dialog_test.html
+++ b/polygerrit-ui/app/elements/core/gr-keyboard-shortcuts-dialog/gr-keyboard-shortcuts-dialog_test.html
@@ -17,7 +17,7 @@
 -->
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-key-binding-display</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>
diff --git a/polygerrit-ui/app/elements/core/gr-main-header/gr-main-header.js b/polygerrit-ui/app/elements/core/gr-main-header/gr-main-header.js
index 773ad68..1ae1e93 100644
--- a/polygerrit-ui/app/elements/core/gr-main-header/gr-main-header.js
+++ b/polygerrit-ui/app/elements/core/gr-main-header/gr-main-header.js
@@ -69,94 +69,107 @@
     'CUSTOM_EXTENSION',
   ]);
 
-  Polymer({
-    is: 'gr-main-header',
+  /**
+    * @appliesMixin Gerrit.AdminNavMixin
+    * @appliesMixin Gerrit.BaseUrlMixin
+    * @appliesMixin Gerrit.DocsUrlMixin
+    * @appliesMixin Gerrit.FireMixin
+    */
+  class GrMainHeader extends Polymer.mixinBehaviors( [
+    Gerrit.AdminNavBehavior,
+    Gerrit.BaseUrlBehavior,
+    Gerrit.DocsUrlBehavior,
+    Gerrit.FireBehavior,
+  ], Polymer.GestureEventListeners(
+      Polymer.LegacyElementMixin(
+          Polymer.Element))) {
+    static get is() { return 'gr-main-header'; }
 
-    hostAttributes: {
-      role: 'banner',
-    },
-
-    properties: {
-      searchQuery: {
-        type: String,
-        notify: true,
-      },
-      loggedIn: {
-        type: Boolean,
-        reflectToAttribute: true,
-      },
-      loading: {
-        type: Boolean,
-        reflectToAttribute: true,
-      },
-
-      /** @type {?Object} */
-      _account: Object,
-      _adminLinks: {
-        type: Array,
-        value() { return []; },
-      },
-      _defaultLinks: {
-        type: Array,
-        value() {
-          return DEFAULT_LINKS;
+    static get properties() {
+      return {
+        searchQuery: {
+          type: String,
+          notify: true,
         },
-      },
-      _docBaseUrl: {
-        type: String,
-        value: null,
-      },
-      _links: {
-        type: Array,
-        computed: '_computeLinks(_defaultLinks, _userLinks, _adminLinks, ' +
+        loggedIn: {
+          type: Boolean,
+          reflectToAttribute: true,
+        },
+        loading: {
+          type: Boolean,
+          reflectToAttribute: true,
+        },
+
+        /** @type {?Object} */
+        _account: Object,
+        _adminLinks: {
+          type: Array,
+          value() { return []; },
+        },
+        _defaultLinks: {
+          type: Array,
+          value() {
+            return DEFAULT_LINKS;
+          },
+        },
+        _docBaseUrl: {
+          type: String,
+          value: null,
+        },
+        _links: {
+          type: Array,
+          computed: '_computeLinks(_defaultLinks, _userLinks, _adminLinks, ' +
             '_topMenus, _docBaseUrl)',
-      },
-      _loginURL: {
-        type: String,
-        value: '/login',
-      },
-      _userLinks: {
-        type: Array,
-        value() { return []; },
-      },
-      _topMenus: {
-        type: Array,
-        value() { return []; },
-      },
-      _registerText: {
-        type: String,
-        value: 'Sign up',
-      },
-      _registerURL: {
-        type: String,
-        value: null,
-      },
-    },
+        },
+        _loginURL: {
+          type: String,
+          value: '/login',
+        },
+        _userLinks: {
+          type: Array,
+          value() { return []; },
+        },
+        _topMenus: {
+          type: Array,
+          value() { return []; },
+        },
+        _registerText: {
+          type: String,
+          value: 'Sign up',
+        },
+        _registerURL: {
+          type: String,
+          value: null,
+        },
+      };
+    }
 
-    behaviors: [
-      Gerrit.AdminNavBehavior,
-      Gerrit.BaseUrlBehavior,
-      Gerrit.DocsUrlBehavior,
-      Gerrit.FireBehavior,
-    ],
+    static get observers() {
+      return [
+        '_accountLoaded(_account)',
+      ];
+    }
 
-    observers: [
-      '_accountLoaded(_account)',
-    ],
+    ready() {
+      super.ready();
+      this._ensureAttribute('role', 'banner');
+    }
 
     attached() {
+      super.attached();
       this._loadAccount();
       this._loadConfig();
       this.listen(window, 'location-change', '_handleLocationChange');
-    },
+    }
 
     detached() {
+      super.detached();
       this.unlisten(window, 'location-change', '_handleLocationChange');
-    },
+    }
 
     reload() {
       this._loadAccount();
-    },
+    }
 
     _handleLocationChange(e) {
       const baseUrl = this.getBaseUrl();
@@ -173,11 +186,11 @@
             window.location.search +
             window.location.hash);
       }
-    },
+    }
 
     _computeRelativeURL(path) {
       return '//' + window.location.host + this.getBaseUrl() + path;
-    },
+    }
 
     _computeLinks(defaultLinks, userLinks, adminLinks, topMenus, docBaseUrl) {
       // Polymer 2: check for undefined
@@ -232,7 +245,7 @@
         }
       }
       return links;
-    },
+    }
 
     _getDocLinks(docBaseUrl, docLinks) {
       if (!docBaseUrl || !docLinks) {
@@ -249,7 +262,7 @@
           target: '_blank',
         };
       });
-    },
+    }
 
     _loadAccount() {
       this.loading = true;
@@ -273,7 +286,7 @@
               this._adminLinks = res.links;
             });
       });
-    },
+    }
 
     _loadConfig() {
       this.$.restAPI.getConfig()
@@ -282,7 +295,7 @@
             return this.getDocsBaseUrl(config, this.$.restAPI);
           })
           .then(docBaseUrl => { this._docBaseUrl = docBaseUrl; });
-    },
+    }
 
     _accountLoaded(account) {
       if (!account) { return; }
@@ -290,7 +303,7 @@
       this.$.restAPI.getPreferences().then(prefs => {
         this._userLinks = prefs.my.map(this._fixCustomMenuItem);
       });
-    },
+    }
 
     _retrieveRegisterURL(config) {
       if (AUTH_TYPES_WITH_REGISTER_URL.has(config.auth.auth_type)) {
@@ -299,11 +312,11 @@
           this._registerText = config.auth.register_text;
         }
       }
-    },
+    }
 
     _computeIsInvisible(registerURL) {
       return registerURL ? '' : 'invisible';
-    },
+    }
 
     _fixCustomMenuItem(linkObj) {
       // Normalize all urls to PolyGerrit style.
@@ -321,17 +334,17 @@
       delete linkObj.target;
 
       return linkObj;
-    },
+    }
 
     _generateSettingsLink() {
       return this.getBaseUrl() + '/settings/';
-    },
+    }
 
     _onMobileSearchTap(e) {
       e.preventDefault();
       e.stopPropagation();
       this.fire('mobile-search', null, {bubbles: false});
-    },
+    }
 
     _computeLinkGroupClass(linkGroup) {
       if (linkGroup && linkGroup.class) {
@@ -339,6 +352,8 @@
       }
 
       return '';
-    },
-  });
+    }
+  }
+
+  customElements.define(GrMainHeader.is, GrMainHeader);
 })();
diff --git a/polygerrit-ui/app/elements/core/gr-main-header/gr-main-header_test.html b/polygerrit-ui/app/elements/core/gr-main-header/gr-main-header_test.html
index ea0056f..46e7d32 100644
--- a/polygerrit-ui/app/elements/core/gr-main-header/gr-main-header_test.html
+++ b/polygerrit-ui/app/elements/core/gr-main-header/gr-main-header_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-main-header</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>
diff --git a/polygerrit-ui/app/elements/core/gr-navigation/gr-navigation_test.html b/polygerrit-ui/app/elements/core/gr-navigation/gr-navigation_test.html
index 73ce86a..ca44430 100644
--- a/polygerrit-ui/app/elements/core/gr-navigation/gr-navigation_test.html
+++ b/polygerrit-ui/app/elements/core/gr-navigation/gr-navigation_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-navigation</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>
diff --git a/polygerrit-ui/app/elements/core/gr-reporting/gr-reporting_test.html b/polygerrit-ui/app/elements/core/gr-reporting/gr-reporting_test.html
index 736fa54..c2c0297 100644
--- a/polygerrit-ui/app/elements/core/gr-reporting/gr-reporting_test.html
+++ b/polygerrit-ui/app/elements/core/gr-reporting/gr-reporting_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-reporting</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>
diff --git a/polygerrit-ui/app/elements/core/gr-router/gr-router.js b/polygerrit-ui/app/elements/core/gr-router/gr-router.js
index 7e47912..a3bf6c9 100644
--- a/polygerrit-ui/app/elements/core/gr-router/gr-router.js
+++ b/polygerrit-ui/app/elements/core/gr-router/gr-router.js
@@ -207,38 +207,46 @@
     });
   })();
 
-  Polymer({
-    is: 'gr-router',
+  /**
+    * @appliesMixin Gerrit.BaseUrlMixin
+    * @appliesMixin Gerrit.FireMixin
+    * @appliesMixin Gerrit.PatchSetMixin
+    * @appliesMixin Gerrit.URLEncodingMixin
+    */
+  class GrRouter extends Polymer.mixinBehaviors( [
+    Gerrit.BaseUrlBehavior,
+    Gerrit.FireBehavior,
+    Gerrit.PatchSetBehavior,
+    Gerrit.URLEncodingBehavior,
+  ], Polymer.GestureEventListeners(
+      Polymer.LegacyElementMixin(
+          Polymer.Element))) {
+    static get is() { return 'gr-router'; }
 
-    properties: {
-      _app: {
-        type: Object,
-        value: app,
-      },
-      _isRedirecting: Boolean,
-      // This variable is to differentiate between internal navigation (false)
-      // and for first navigation in app after loaded from server (true).
-      _isInitialLoad: {
-        type: Boolean,
-        value: true,
-      },
-    },
-
-    behaviors: [
-      Gerrit.BaseUrlBehavior,
-      Gerrit.FireBehavior,
-      Gerrit.PatchSetBehavior,
-      Gerrit.URLEncodingBehavior,
-    ],
+    static get properties() {
+      return {
+        _app: {
+          type: Object,
+          value: app,
+        },
+        _isRedirecting: Boolean,
+        // This variable is to differentiate between internal navigation (false)
+        // and for first navigation in app after loaded from server (true).
+        _isInitialLoad: {
+          type: Boolean,
+          value: true,
+        },
+      };
+    }
 
     start() {
       if (!this._app) { return; }
       this._startRouter();
-    },
+    }
 
     _setParams(params) {
       this._appElement().params = params;
-    },
+    }
 
     _appElement() {
       // In Polymer2 you have to reach through the shadow root of the app
@@ -248,12 +256,12 @@
       return document.getElementById('app-element') ||
           document.getElementById('app').shadowRoot.getElementById(
               'app-element');
-    },
+    }
 
     _redirect(url) {
       this._isRedirecting = true;
       page.redirect(url);
-    },
+    }
 
     /**
      * @param {!Object} params
@@ -285,7 +293,7 @@
       }
 
       return base + url;
-    },
+    }
 
     _generateWeblinks(params) {
       const type = params.type;
@@ -299,7 +307,7 @@
         default:
           console.warn(`Unsupported weblink ${type}!`);
       }
-    },
+    }
 
     _getPatchSetWeblink(params) {
       const {commit, options} = params;
@@ -311,7 +319,7 @@
       } else {
         return {name, url: weblink.url};
       }
-    },
+    }
 
     _firstCodeBrowserWeblink(weblinks) {
       // This is an ordered whitelist of web link types that provide direct
@@ -323,7 +331,7 @@
         if (weblink) { return weblink; }
       }
       return null;
-    },
+    }
 
     _getBrowseCommitWeblink(weblinks, config) {
       if (!weblinks) { return null; }
@@ -339,7 +347,7 @@
       }
       if (!weblink) { return null; }
       return weblink;
-    },
+    }
 
     _getChangeWeblinks({repo, commit, options: {weblinks, config}}) {
       if (!weblinks || !weblinks.length) return [];
@@ -348,11 +356,11 @@
         !commitWeblink ||
         !commitWeblink.name ||
         weblink.name !== commitWeblink.name);
-    },
+    }
 
     _getFileWebLinks({repo, commit, file, options: {weblinks}}) {
       return weblinks;
-    },
+    }
 
     /**
      * @param {!Object} params
@@ -399,7 +407,7 @@
       }
 
       return '/q/' + operators.join('+') + offsetExpr;
-    },
+    }
 
     /**
      * @param {!Object} params
@@ -423,7 +431,7 @@
       } else {
         return `/c/${params.changeNum}${suffix}`;
       }
-    },
+    }
 
     /**
      * @param {!Object} params
@@ -448,7 +456,7 @@
         // User dashboard.
         return `/dashboard/${params.user || 'self'}`;
       }
-    },
+    }
 
     /**
      * @param {!Array<!{name: string, query: string}>} sections
@@ -465,7 +473,7 @@
         return encodeURIComponent(section.name) + '=' +
             encodeURIComponent(query);
       });
-    },
+    }
 
     /**
      * @param {!Object} params
@@ -491,7 +499,7 @@
       } else {
         return `/c/${params.changeNum}${suffix}`;
       }
-    },
+    }
 
     /**
      * @param {!Object} params
@@ -505,7 +513,7 @@
         url += ',audit-log';
       }
       return url;
-    },
+    }
 
     /**
      * @param {!Object} params
@@ -525,7 +533,7 @@
         url += ',dashboards';
       }
       return url;
-    },
+    }
 
     /**
      * @param {!Object} params
@@ -533,7 +541,7 @@
      */
     _generateSettingsUrl(params) {
       return '/settings';
-    },
+    }
 
     /**
      * Given an object of parameters, potentially including a `patchNum` or a
@@ -547,7 +555,7 @@
       if (params.patchNum) { range = '' + params.patchNum; }
       if (params.basePatchNum) { range = params.basePatchNum + '..' + range; }
       return range;
-    },
+    }
 
     /**
      * Given a set of params without a project, gets the project from the rest
@@ -571,7 +579,7 @@
             this._normalizePatchRangeParams(params);
             this._redirect(this._generateUrl(params));
           });
-    },
+    }
 
     /**
      * Normalizes the params object, and determines if the URL needs to be
@@ -600,7 +608,7 @@
         params.basePatchNum = null;
       }
       return needsRedirect;
-    },
+    }
 
     /**
      * Redirect the user to login using the given return-URL for redirection
@@ -611,7 +619,7 @@
       const basePath = this.getBaseUrl() || '';
       page(
           '/login/' + encodeURIComponent(returnUrl.substring(basePath.length)));
-    },
+    }
 
     /**
      * Hashes parsed by page.js exclude "inner" hashes, so a URL like "/a#b#c"
@@ -622,7 +630,7 @@
      */
     _getHashFromCanonicalPath(canonicalPath) {
       return canonicalPath.split('#').slice(1).join('#');
-    },
+    }
 
     _parseLineAddress(hash) {
       const match = hash.match(LINE_ADDRESS_PATTERN);
@@ -631,7 +639,7 @@
         leftSide: !!match[1],
         lineNum: parseInt(match[2], 10),
       };
-    },
+    }
 
     /**
      * Check to see if the user is logged in and return a promise that only
@@ -650,12 +658,12 @@
           return Promise.reject(new Error());
         }
       });
-    },
+    }
 
     /**  Page.js middleware that warms the REST API's logged-in cache line. */
     _loadUserMiddleware(ctx, next) {
       this.$.restAPI.getLoggedIn().then(() => { next(); });
-    },
+    }
 
     /**
      * Map a route to a method on the router.
@@ -682,7 +690,7 @@
           this._redirectIfNotLoggedIn(data) : Promise.resolve();
         promise.then(() => { this[handlerName](data); });
       });
-    },
+    }
 
     _startRouter() {
       const base = this.getBaseUrl();
@@ -878,7 +886,7 @@
       this._mapRoute(RoutePattern.DEFAULT, '_handleDefaultRoute');
 
       page.start();
-    },
+    }
 
     /**
      * @param {!Object} data
@@ -919,7 +927,7 @@
           this._redirect('/q/status:open');
         }
       });
-    },
+    }
 
     /**
      * Decode an application/x-www-form-urlencoded string.
@@ -929,7 +937,7 @@
      */
     _decodeQueryString(qs) {
       return decodeURIComponent(qs.replace(PLUS_PATTERN, ' '));
-    },
+    }
 
     /**
      * Parse a query string (e.g. window.location.search) into an array of
@@ -961,7 +969,7 @@
         }
       });
       return params;
-    },
+    }
 
     /**
      * Handle dashboard routes. These may be user, or project dashboards.
@@ -986,7 +994,7 @@
           });
         }
       });
-    },
+    }
 
     /**
      * Handle custom dashboard routes.
@@ -1038,7 +1046,7 @@
       // Redirect /dashboard/ -> /dashboard/self.
       this._redirect('/dashboard/self');
       return Promise.resolve();
-    },
+    }
 
     _handleProjectDashboardRoute(data) {
       const project = data.params[0];
@@ -1048,22 +1056,22 @@
         dashboard: decodeURIComponent(data.params[1]),
       });
       this.$.reporting.setRepoName(project);
-    },
+    }
 
     _handleGroupInfoRoute(data) {
       this._redirect('/admin/groups/' + encodeURIComponent(data.params[0]));
-    },
+    }
 
     _handleGroupSelfRedirectRoute(data) {
       this._redirect('/settings/#Groups');
-    },
+    }
 
     _handleGroupRoute(data) {
       this._setParams({
         view: Gerrit.Nav.View.GROUP,
         groupId: data.params[0],
       });
-    },
+    }
 
     _handleGroupAuditLogRoute(data) {
       this._setParams({
@@ -1071,7 +1079,7 @@
         detail: Gerrit.Nav.GroupDetailView.LOG,
         groupId: data.params[0],
       });
-    },
+    }
 
     _handleGroupMembersRoute(data) {
       this._setParams({
@@ -1079,7 +1087,7 @@
         detail: Gerrit.Nav.GroupDetailView.MEMBERS,
         groupId: data.params[0],
       });
-    },
+    }
 
     _handleGroupListOffsetRoute(data) {
       this._setParams({
@@ -1089,7 +1097,7 @@
         filter: null,
         openCreateModal: data.hash === 'create',
       });
-    },
+    }
 
     _handleGroupListFilterOffsetRoute(data) {
       this._setParams({
@@ -1098,7 +1106,7 @@
         offset: data.params.offset,
         filter: data.params.filter,
       });
-    },
+    }
 
     _handleGroupListFilterRoute(data) {
       this._setParams({
@@ -1106,7 +1114,7 @@
         adminView: 'gr-admin-group-list',
         filter: data.params.filter || null,
       });
-    },
+    }
 
     _handleProjectsOldRoute(data) {
       let params = '';
@@ -1119,7 +1127,7 @@
       }
 
       this._redirect(`/admin/repos/${params}`);
-    },
+    }
 
     _handleRepoCommandsRoute(data) {
       const repo = data.params[0];
@@ -1129,7 +1137,7 @@
         repo,
       });
       this.$.reporting.setRepoName(repo);
-    },
+    }
 
     _handleRepoAccessRoute(data) {
       const repo = data.params[0];
@@ -1139,7 +1147,7 @@
         repo,
       });
       this.$.reporting.setRepoName(repo);
-    },
+    }
 
     _handleRepoDashboardsRoute(data) {
       const repo = data.params[0];
@@ -1149,7 +1157,7 @@
         repo,
       });
       this.$.reporting.setRepoName(repo);
-    },
+    }
 
     _handleBranchListOffsetRoute(data) {
       this._setParams({
@@ -1159,7 +1167,7 @@
         offset: data.params[2] || 0,
         filter: null,
       });
-    },
+    }
 
     _handleBranchListFilterOffsetRoute(data) {
       this._setParams({
@@ -1169,7 +1177,7 @@
         offset: data.params.offset,
         filter: data.params.filter,
       });
-    },
+    }
 
     _handleBranchListFilterRoute(data) {
       this._setParams({
@@ -1178,7 +1186,7 @@
         repo: data.params.repo,
         filter: data.params.filter || null,
       });
-    },
+    }
 
     _handleTagListOffsetRoute(data) {
       this._setParams({
@@ -1188,7 +1196,7 @@
         offset: data.params[2] || 0,
         filter: null,
       });
-    },
+    }
 
     _handleTagListFilterOffsetRoute(data) {
       this._setParams({
@@ -1198,7 +1206,7 @@
         offset: data.params.offset,
         filter: data.params.filter,
       });
-    },
+    }
 
     _handleTagListFilterRoute(data) {
       this._setParams({
@@ -1207,7 +1215,7 @@
         repo: data.params.repo,
         filter: data.params.filter || null,
       });
-    },
+    }
 
     _handleRepoListOffsetRoute(data) {
       this._setParams({
@@ -1217,7 +1225,7 @@
         filter: null,
         openCreateModal: data.hash === 'create',
       });
-    },
+    }
 
     _handleRepoListFilterOffsetRoute(data) {
       this._setParams({
@@ -1226,7 +1234,7 @@
         offset: data.params.offset,
         filter: data.params.filter,
       });
-    },
+    }
 
     _handleRepoListFilterRoute(data) {
       this._setParams({
@@ -1234,19 +1242,19 @@
         adminView: 'gr-repo-list',
         filter: data.params.filter || null,
       });
-    },
+    }
 
     _handleCreateProjectRoute(data) {
       // Redirects the legacy route to the new route, which displays the project
       // list with a hash 'create'.
       this._redirect('/admin/repos#create');
-    },
+    }
 
     _handleCreateGroupRoute(data) {
       // Redirects the legacy route to the new route, which displays the group
       // list with a hash 'create'.
       this._redirect('/admin/groups#create');
-    },
+    }
 
     _handleRepoRoute(data) {
       const repo = data.params[0];
@@ -1255,7 +1263,7 @@
         repo,
       });
       this.$.reporting.setRepoName(repo);
-    },
+    }
 
     _handlePluginListOffsetRoute(data) {
       this._setParams({
@@ -1264,7 +1272,7 @@
         offset: data.params[1] || 0,
         filter: null,
       });
-    },
+    }
 
     _handlePluginListFilterOffsetRoute(data) {
       this._setParams({
@@ -1273,7 +1281,7 @@
         offset: data.params.offset,
         filter: data.params.filter,
       });
-    },
+    }
 
     _handlePluginListFilterRoute(data) {
       this._setParams({
@@ -1281,14 +1289,14 @@
         adminView: 'gr-plugin-list',
         filter: data.params.filter || null,
       });
-    },
+    }
 
     _handlePluginListRoute(data) {
       this._setParams({
         view: Gerrit.Nav.View.ADMIN,
         adminView: 'gr-plugin-list',
       });
-    },
+    }
 
     _handleQueryRoute(data) {
       this._setParams({
@@ -1296,15 +1304,15 @@
         query: data.params[0],
         offset: data.params[2],
       });
-    },
+    }
 
     _handleQueryLegacySuffixRoute(ctx) {
       this._redirect(ctx.path.replace(LEGACY_QUERY_SUFFIX_PATTERN, ''));
-    },
+    }
 
     _handleChangeNumberLegacyRoute(ctx) {
       this._redirect('/c/' + encodeURIComponent(ctx.params[0]));
-    },
+    }
 
     _handleChangeRoute(ctx) {
       // Parameter order is based on the regex group number matched.
@@ -1318,7 +1326,7 @@
 
       this.$.reporting.setRepoName(params.project);
       this._redirectOrNavigate(params);
-    },
+    }
 
     _handleDiffRoute(ctx) {
       // Parameter order is based on the regex group number matched.
@@ -1338,7 +1346,7 @@
       }
       this.$.reporting.setRepoName(params.project);
       this._redirectOrNavigate(params);
-    },
+    }
 
     _handleChangeLegacyRoute(ctx) {
       // Parameter order is based on the regex group number matched.
@@ -1351,11 +1359,11 @@
       };
 
       this._normalizeLegacyRouteParams(params);
-    },
+    }
 
     _handleLegacyLinenum(ctx) {
       this._redirect(ctx.path.replace(LEGACY_LINENUM_PATTERN, '#$1'));
-    },
+    }
 
     _handleDiffLegacyRoute(ctx) {
       // Parameter order is based on the regex group number matched.
@@ -1374,7 +1382,7 @@
       }
 
       this._normalizeLegacyRouteParams(params);
-    },
+    }
 
     _handleDiffEditRoute(ctx) {
       // Parameter order is based on the regex group number matched.
@@ -1387,7 +1395,7 @@
         view: Gerrit.Nav.View.EDIT,
       });
       this.$.reporting.setRepoName(project);
-    },
+    }
 
     _handleChangeEditRoute(ctx) {
       // Parameter order is based on the regex group number matched.
@@ -1400,7 +1408,7 @@
         edit: true,
       });
       this.$.reporting.setRepoName(project);
-    },
+    }
 
     /**
      * Normalize the patch range params for a the change or diff view and
@@ -1413,16 +1421,16 @@
       } else {
         this._setParams(params);
       }
-    },
+    }
 
     _handleAgreementsRoute() {
       this._redirect('/settings/#Agreements');
-    },
+    }
 
     _handleNewAgreementsRoute(data) {
       data.params.view = Gerrit.Nav.View.AGREEMENTS;
       this._setParams(data.params);
-    },
+    }
 
     _handleSettingsLegacyRoute(data) {
       // email tokens may contain '+' but no space.
@@ -1433,11 +1441,11 @@
         view: Gerrit.Nav.View.SETTINGS,
         emailToken: token,
       });
-    },
+    }
 
     _handleSettingsRoute(data) {
       this._setParams({view: Gerrit.Nav.View.SETTINGS});
-    },
+    }
 
     _handleRegisterRoute(ctx) {
       this._setParams({justRegistered: true});
@@ -1448,7 +1456,7 @@
 
       if (path[0] !== '/') { return; }
       this._redirect(this.getBaseUrl() + path);
-    },
+    }
 
     /**
      * Handler for routes that should pass through the router and not be caught
@@ -1456,7 +1464,7 @@
      */
     _handlePassThroughRoute() {
       location.reload();
-    },
+    }
 
     /**
      * URL may sometimes have /+/ encoded to / /.
@@ -1466,26 +1474,26 @@
       let hash = this._getHashFromCanonicalPath(ctx.canonicalPath);
       if (hash.length) { hash = '#' + hash; }
       this._redirect(`/c/${ctx.params[0]}/+/${ctx.params[1]}${hash}`);
-    },
+    }
 
     _handlePluginScreen(ctx) {
       const view = Gerrit.Nav.View.PLUGIN_SCREEN;
       const plugin = ctx.params[0];
       const screen = ctx.params[1];
       this._setParams({view, plugin, screen});
-    },
+    }
 
     _handleDocumentationSearchRoute(data) {
       this._setParams({
         view: Gerrit.Nav.View.DOCUMENTATION_SEARCH,
         filter: data.params.filter || null,
       });
-    },
+    }
 
     _handleDocumentationSearchRedirectRoute(data) {
       this._redirect('/Documentation/q/filter:' +
           encodeURIComponent(data.params[0]));
-    },
+    }
 
     _handleDocumentationRedirectRoute(data) {
       if (data.params[1]) {
@@ -1494,7 +1502,7 @@
         // Redirect /Documentation to /Documentation/index.html
         this._redirect('/Documentation/index.html');
       }
-    },
+    }
 
     /**
      * Catchall route for when no other route is matched.
@@ -1507,7 +1515,7 @@
         // Route can be recognized by server, so we pass it to server.
         this._handlePassThroughRoute();
       }
-    },
+    }
 
     _show404() {
       // Note: the app's 404 display is tightly-coupled with catching 404
@@ -1515,6 +1523,8 @@
       // TODO: Decouple the gr-app error view from network responses.
       this._appElement().dispatchEvent(new CustomEvent('page-error',
           {detail: {response: {status: 404}}}));
-    },
-  });
+    }
+  }
+
+  customElements.define(GrRouter.is, GrRouter);
 })();
diff --git a/polygerrit-ui/app/elements/core/gr-router/gr-router_test.html b/polygerrit-ui/app/elements/core/gr-router/gr-router_test.html
index 4bfc35b..fb7f0d6 100644
--- a/polygerrit-ui/app/elements/core/gr-router/gr-router_test.html
+++ b/polygerrit-ui/app/elements/core/gr-router/gr-router_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-router</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>
diff --git a/polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar.js b/polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar.js
index 0e4436b2..4ae1f20 100644
--- a/polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar.js
+++ b/polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar.js
@@ -103,75 +103,80 @@
 
   const TOKENIZE_REGEX = /(?:[^\s"]+|"[^"]*")+\s*/g;
 
-  Polymer({
-    is: 'gr-search-bar',
-
+  /**
+    * @appliesMixin Gerrit.KeyboardShortcutMixin
+    * @appliesMixin Gerrit.URLEncodingMixin
+    */
+  class GrSearchBar extends Polymer.mixinBehaviors( [
+    Gerrit.KeyboardShortcutBehavior,
+    Gerrit.URLEncodingBehavior,
+  ], Polymer.GestureEventListeners(
+      Polymer.LegacyElementMixin(
+          Polymer.Element))) {
+    static get is() { return 'gr-search-bar'; }
     /**
      * Fired when a search is committed
      *
      * @event handle-search
      */
 
-    behaviors: [
-      Gerrit.KeyboardShortcutBehavior,
-      Gerrit.URLEncodingBehavior,
-    ],
-
-    properties: {
-      value: {
-        type: String,
-        value: '',
-        notify: true,
-        observer: '_valueChanged',
-      },
-      keyEventTarget: {
-        type: Object,
-        value() { return document.body; },
-      },
-      query: {
-        type: Function,
-        value() {
-          return this._getSearchSuggestions.bind(this);
+    static get properties() {
+      return {
+        value: {
+          type: String,
+          value: '',
+          notify: true,
+          observer: '_valueChanged',
         },
-      },
-      projectSuggestions: {
-        type: Function,
-        value() {
-          return () => Promise.resolve([]);
+        keyEventTarget: {
+          type: Object,
+          value() { return document.body; },
         },
-      },
-      groupSuggestions: {
-        type: Function,
-        value() {
-          return () => Promise.resolve([]);
+        query: {
+          type: Function,
+          value() {
+            return this._getSearchSuggestions.bind(this);
+          },
         },
-      },
-      accountSuggestions: {
-        type: Function,
-        value() {
-          return () => Promise.resolve([]);
+        projectSuggestions: {
+          type: Function,
+          value() {
+            return () => Promise.resolve([]);
+          },
         },
-      },
-      _inputVal: String,
-      _threshold: {
-        type: Number,
-        value: 1,
-      },
-    },
+        groupSuggestions: {
+          type: Function,
+          value() {
+            return () => Promise.resolve([]);
+          },
+        },
+        accountSuggestions: {
+          type: Function,
+          value() {
+            return () => Promise.resolve([]);
+          },
+        },
+        _inputVal: String,
+        _threshold: {
+          type: Number,
+          value: 1,
+        },
+      };
+    }
 
     keyboardShortcuts() {
       return {
         [this.Shortcut.SEARCH]: '_handleSearch',
       };
-    },
+    }
 
     _valueChanged(value) {
       this._inputVal = value;
-    },
+    }
 
     _handleInputCommit(e) {
       this._preventDefaultAndNavigateToInputVal(e);
-    },
+    }
 
     /**
      * This function is called in a few different cases:
@@ -205,7 +210,7 @@
           detail: {inputVal: this._inputVal},
         }));
       }
-    },
+    }
 
     /**
      * Determine what array of possible suggestions should be provided
@@ -247,7 +252,7 @@
               .filter(operator => operator.includes(input))
               .map(operator => ({text: operator})));
       }
-    },
+    }
 
     /**
      * Get the sorted, pruned list of suggestions for the current search query.
@@ -290,7 +295,7 @@
                   };
                 });
           });
-    },
+    }
 
     _handleSearch(e) {
       const keyboardEvent = this.getKeyboardEvent(e);
@@ -300,6 +305,8 @@
       e.preventDefault();
       this.$.searchInput.focus();
       this.$.searchInput.selectAll();
-    },
-  });
+    }
+  }
+
+  customElements.define(GrSearchBar.is, GrSearchBar);
 })();
diff --git a/polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar_test.html b/polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar_test.html
index a4927c3..c822b73 100644
--- a/polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar_test.html
+++ b/polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-search-bar</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>
diff --git a/polygerrit-ui/app/elements/core/gr-smart-search/gr-smart-search.js b/polygerrit-ui/app/elements/core/gr-smart-search/gr-smart-search.js
index 7dff30b..03a2c0d 100644
--- a/polygerrit-ui/app/elements/core/gr-smart-search/gr-smart-search.js
+++ b/polygerrit-ui/app/elements/core/gr-smart-search/gr-smart-search.js
@@ -21,52 +21,58 @@
   const SELF_EXPRESSION = 'self';
   const ME_EXPRESSION = 'me';
 
-  Polymer({
-    is: 'gr-smart-search',
+  /**
+    * @appliesMixin Gerrit.DisplayNameMixin
+    */
+  class GrSmartSearch extends Polymer.mixinBehaviors( [
+    Gerrit.DisplayNameBehavior,
+  ], Polymer.GestureEventListeners(
+      Polymer.LegacyElementMixin(
+          Polymer.Element))) {
+    static get is() { return 'gr-smart-search'; }
 
-    properties: {
-      searchQuery: String,
-      _config: Object,
-      _projectSuggestions: {
-        type: Function,
-        value() {
-          return this._fetchProjects.bind(this);
+    static get properties() {
+      return {
+        searchQuery: String,
+        _config: Object,
+        _projectSuggestions: {
+          type: Function,
+          value() {
+            return this._fetchProjects.bind(this);
+          },
         },
-      },
-      _groupSuggestions: {
-        type: Function,
-        value() {
-          return this._fetchGroups.bind(this);
+        _groupSuggestions: {
+          type: Function,
+          value() {
+            return this._fetchGroups.bind(this);
+          },
         },
-      },
-      _accountSuggestions: {
-        type: Function,
-        value() {
-          return this._fetchAccounts.bind(this);
+        _accountSuggestions: {
+          type: Function,
+          value() {
+            return this._fetchAccounts.bind(this);
+          },
         },
-      },
-    },
-
-    behaviors: [
-      Gerrit.DisplayNameBehavior,
-    ],
+      };
+    }
 
     attached() {
+      super.attached();
       this.$.restAPI.getConfig().then(cfg => {
         this._config = cfg;
       });
-    },
+    }
 
     _handleSearch(e) {
       const input = e.detail.inputVal;
       if (input) {
         Gerrit.Nav.navigateToSearchQuery(input);
       }
-    },
+    }
 
     _accountOrAnon(name) {
       return this.getUserName(this._serverConfig, name, false);
-    },
+    }
 
     /**
      * Fetch from the API the predicted projects.
@@ -86,7 +92,7 @@
             const keys = Object.keys(projects);
             return keys.map(key => ({text: predicate + ':' + key}));
           });
-    },
+    }
 
     /**
      * Fetch from the API the predicted groups.
@@ -107,7 +113,7 @@
             const keys = Object.keys(groups);
             return keys.map(key => ({text: predicate + ':' + key}));
           });
-    },
+    }
 
     /**
      * Fetch from the API the predicted accounts.
@@ -138,7 +144,7 @@
               return accounts;
             }
           });
-    },
+    }
 
     _mapAccountsHelper(accounts, predicate) {
       return accounts.map(account => ({
@@ -147,6 +153,8 @@
           `${predicate}:${account.email}` :
           `${predicate}:"${this._accountOrAnon(account)}"`,
       }));
-    },
-  });
+    }
+  }
+
+  customElements.define(GrSmartSearch.is, GrSmartSearch);
 })();
diff --git a/polygerrit-ui/app/elements/core/gr-smart-search/gr-smart-search_test.html b/polygerrit-ui/app/elements/core/gr-smart-search/gr-smart-search_test.html
index 53036dc..5a4e341 100644
--- a/polygerrit-ui/app/elements/core/gr-smart-search/gr-smart-search_test.html
+++ b/polygerrit-ui/app/elements/core/gr-smart-search/gr-smart-search_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-smart-search</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>
diff --git a/polygerrit-ui/app/elements/custom-dark-theme_test.html b/polygerrit-ui/app/elements/custom-dark-theme_test.html
index 4cf35f1..1cd042a 100644
--- a/polygerrit-ui/app/elements/custom-dark-theme_test.html
+++ b/polygerrit-ui/app/elements/custom-dark-theme_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-app-it_test</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>
diff --git a/polygerrit-ui/app/elements/custom-light-theme_test.html b/polygerrit-ui/app/elements/custom-light-theme_test.html
index e346af5..0cabd09 100644
--- a/polygerrit-ui/app/elements/custom-light-theme_test.html
+++ b/polygerrit-ui/app/elements/custom-light-theme_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-app-it_test</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>
diff --git a/polygerrit-ui/app/elements/diff/gr-comment-api/gr-comment-api-mock.js b/polygerrit-ui/app/elements/diff/gr-comment-api/gr-comment-api-mock.js
index b7994e6..4009420 100644
--- a/polygerrit-ui/app/elements/diff/gr-comment-api/gr-comment-api-mock.js
+++ b/polygerrit-ui/app/elements/diff/gr-comment-api/gr-comment-api-mock.js
@@ -17,16 +17,20 @@
 (function() {
   'use strict';
 
-  Polymer({
-    is: 'comment-api-mock',
+  class CommentApiMock extends Polymer.GestureEventListeners(
+      Polymer.LegacyElementMixin(
+          Polymer.Element)) {
+    static get is() { return 'comment-api-mock'; }
 
-    properties: {
-      _changeComments: Object,
-    },
+    static get properties() {
+      return {
+        _changeComments: Object,
+      };
+    }
 
     loadComments() {
       return this._reloadComments();
-    },
+    }
 
     /**
      * For the purposes of the mock, _reloadDrafts is not included because its
@@ -38,13 +42,15 @@
       return this._reloadComments().then(() => {
         return e.detail.resolve();
       });
-    },
+    }
 
     _reloadComments() {
       return this.$.commentAPI.loadAll(this._changeNum)
           .then(comments => {
             this._changeComments = this.$.commentAPI._changeComments;
           });
-    },
-  });
+    }
+  }
+
+  customElements.define(CommentApiMock.is, CommentApiMock);
 })();
diff --git a/polygerrit-ui/app/elements/diff/gr-comment-api/gr-comment-api.js b/polygerrit-ui/app/elements/diff/gr-comment-api/gr-comment-api.js
index 1ac307f..e84e996 100644
--- a/polygerrit-ui/app/elements/diff/gr-comment-api/gr-comment-api.js
+++ b/polygerrit-ui/app/elements/diff/gr-comment-api/gr-comment-api.js
@@ -460,20 +460,27 @@
         this._isInRevisionOfPatchRange(comment, range);
   };
 
-  Polymer({
-    is: 'gr-comment-api',
+  /**
+    * @appliesMixin Gerrit.PatchSetMixin
+    */
+  class GrCommentApi extends Polymer.mixinBehaviors( [
+    Gerrit.PatchSetBehavior,
+  ], Polymer.GestureEventListeners(
+      Polymer.LegacyElementMixin(
+          Polymer.Element))) {
+    static get is() { return 'gr-comment-api'; }
 
-    properties: {
-      _changeComments: Object,
-    },
+    static get properties() {
+      return {
+        _changeComments: Object,
+      };
+    }
 
-    listeners: {
-      'reload-drafts': 'reloadDrafts',
-    },
-
-    behaviors: [
-      Gerrit.PatchSetBehavior,
-    ],
+    created() {
+      super.created();
+      this.addEventListener('reload-drafts',
+          changeNum => this.reloadDrafts(changeNum));
+    }
 
     /**
      * Load all comments (with drafts and robot comments) for the given change
@@ -494,7 +501,7 @@
             robotComments, drafts, changeNum);
         return this._changeComments;
       });
-    },
+    }
 
     /**
      * Re-initialize _changeComments with a new ChangeComments object, that
@@ -513,6 +520,8 @@
             this._changeComments.robotComments, drafts, changeNum);
         return this._changeComments;
       });
-    },
-  });
+    }
+  }
+
+  customElements.define(GrCommentApi.is, GrCommentApi);
 })();
diff --git a/polygerrit-ui/app/elements/diff/gr-comment-api/gr-comment-api_test.html b/polygerrit-ui/app/elements/diff/gr-comment-api/gr-comment-api_test.html
index 47181f9..4261c2d 100644
--- a/polygerrit-ui/app/elements/diff/gr-comment-api/gr-comment-api_test.html
+++ b/polygerrit-ui/app/elements/diff/gr-comment-api/gr-comment-api_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-comment-api</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>
diff --git a/polygerrit-ui/app/elements/diff/gr-coverage-layer/gr-coverage-layer.js b/polygerrit-ui/app/elements/diff/gr-coverage-layer/gr-coverage-layer.js
index 3d9c172..d1df4d0 100644
--- a/polygerrit-ui/app/elements/diff/gr-coverage-layer/gr-coverage-layer.js
+++ b/polygerrit-ui/app/elements/diff/gr-coverage-layer/gr-coverage-layer.js
@@ -24,20 +24,23 @@
     [Gerrit.CoverageType.NOT_INSTRUMENTED, 'Not instrumented by any tests.'],
   ]);
 
-  Polymer({
-    is: 'gr-coverage-layer',
+  class GrCoverageLayer extends Polymer.GestureEventListeners(
+      Polymer.LegacyElementMixin(
+          Polymer.Element)) {
+    static get is() { return 'gr-coverage-layer'; }
 
-    properties: {
+    static get properties() {
+      return {
       /**
        * Must be sorted by code_range.start_line.
        * Must only contain ranges that match the side.
        *
        * @type {!Array<!Gerrit.CoverageRange>}
        */
-      coverageRanges: Array,
-      side: String,
+        coverageRanges: Array,
+        side: String,
 
-      /**
+        /**
        * We keep track of the line number from the previous annotate() call,
        * and also of the index of the coverage range that had matched.
        * annotate() calls are coming in with increasing line numbers and
@@ -45,15 +48,16 @@
        * and efficient way for finding the coverage range that matches a given
        * line number.
        */
-      _lineNumber: {
-        type: Number,
-        value: 0,
-      },
-      _index: {
-        type: Number,
-        value: 0,
-      },
-    },
+        _lineNumber: {
+          type: Number,
+          value: 0,
+        },
+        _index: {
+          type: Number,
+          value: 0,
+        },
+      };
+    }
 
     /**
      * Layer method to add annotations to a line.
@@ -102,6 +106,8 @@
         lineNumberEl.title = TOOLTIP_MAP.get(coverageRange.type);
         return;
       }
-    },
-  });
+    }
+  }
+
+  customElements.define(GrCoverageLayer.is, GrCoverageLayer);
 })();
diff --git a/polygerrit-ui/app/elements/diff/gr-coverage-layer/gr-coverage-layer_test.html b/polygerrit-ui/app/elements/diff/gr-coverage-layer/gr-coverage-layer_test.html
index 45a67e1..99c583d 100644
--- a/polygerrit-ui/app/elements/diff/gr-coverage-layer/gr-coverage-layer_test.html
+++ b/polygerrit-ui/app/elements/diff/gr-coverage-layer/gr-coverage-layer_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-coverage-layer</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>
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-unified_test.html b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-unified_test.html
index 19e017d..4c44414 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-unified_test.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-unified_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>GrDiffBuilderUnified</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>
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder_test.html b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder_test.html
index 56fb8f5..758798a 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder_test.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-diff-builder</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>
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-cursor/gr-diff-cursor.js b/polygerrit-ui/app/elements/diff/gr-diff-cursor/gr-diff-cursor.js
index 8848b1c..039a99fa 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-cursor/gr-diff-cursor.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff-cursor/gr-diff-cursor.js
@@ -35,33 +35,36 @@
   const LEFT_SIDE_CLASS = 'target-side-left';
   const RIGHT_SIDE_CLASS = 'target-side-right';
 
-  Polymer({
-    is: 'gr-diff-cursor',
+  class GrDiffCursor extends Polymer.GestureEventListeners(
+      Polymer.LegacyElementMixin(
+          Polymer.Element)) {
+    static get is() { return 'gr-diff-cursor'; }
 
-    properties: {
+    static get properties() {
+      return {
       /**
        * Either DiffSides.LEFT or DiffSides.RIGHT.
        */
-      side: {
-        type: String,
-        value: DiffSides.RIGHT,
-      },
-      /** @type {!HTMLElement|undefined} */
-      diffRow: {
-        type: Object,
-        notify: true,
-        observer: '_rowChanged',
-      },
+        side: {
+          type: String,
+          value: DiffSides.RIGHT,
+        },
+        /** @type {!HTMLElement|undefined} */
+        diffRow: {
+          type: Object,
+          notify: true,
+          observer: '_rowChanged',
+        },
 
-      /**
+        /**
        * The diff views to cursor through and listen to.
        */
-      diffs: {
-        type: Array,
-        value() { return []; },
-      },
+        diffs: {
+          type: Array,
+          value() { return []; },
+        },
 
-      /**
+        /**
        * If set, the cursor will attempt to move to the line number (instead of
        * the first chunk) the next time the diff renders. It is set back to null
        * when used. It should be only used if you want the line to be focused
@@ -71,56 +74,61 @@
        *
        * @type (?number)
        */
-      initialLineNumber: {
-        type: Number,
-        value: null,
-      },
+        initialLineNumber: {
+          type: Number,
+          value: null,
+        },
 
-      /**
+        /**
        * The scroll behavior for the cursor. Values are 'never' and
        * 'keep-visible'. 'keep-visible' will only scroll if the cursor is beyond
        * the viewport.
        */
-      _scrollBehavior: {
-        type: String,
-        value: ScrollBehavior.KEEP_VISIBLE,
-      },
+        _scrollBehavior: {
+          type: String,
+          value: ScrollBehavior.KEEP_VISIBLE,
+        },
 
-      _focusOnMove: {
-        type: Boolean,
-        value: true,
-      },
+        _focusOnMove: {
+          type: Boolean,
+          value: true,
+        },
 
-      _listeningForScroll: Boolean,
-    },
+        _listeningForScroll: Boolean,
+      };
+    }
 
-    observers: [
-      '_updateSideClass(side)',
-      '_diffsChanged(diffs.splices)',
-    ],
+    static get observers() {
+      return [
+        '_updateSideClass(side)',
+        '_diffsChanged(diffs.splices)',
+      ];
+    }
 
     attached() {
+      super.attached();
       // Catch when users are scrolling as the view loads.
       this.listen(window, 'scroll', '_handleWindowScroll');
-    },
+    }
 
     detached() {
+      super.detached();
       this.unlisten(window, 'scroll', '_handleWindowScroll');
-    },
+    }
 
     moveLeft() {
       this.side = DiffSides.LEFT;
       if (this._isTargetBlank()) {
         this.moveUp();
       }
-    },
+    }
 
     moveRight() {
       this.side = DiffSides.RIGHT;
       if (this._isTargetBlank()) {
         this.moveUp();
       }
-    },
+    }
 
     moveDown() {
       if (this._getViewMode() === DiffViewMode.SIDE_BY_SIDE) {
@@ -128,7 +136,7 @@
       } else {
         this.$.cursorManager.next();
       }
-    },
+    }
 
     moveUp() {
       if (this._getViewMode() === DiffViewMode.SIDE_BY_SIDE) {
@@ -136,7 +144,7 @@
       } else {
         this.$.cursorManager.previous();
       }
-    },
+    }
 
     moveToNextChunk(opt_clipToTop) {
       this.$.cursorManager.next(this._isFirstRowOfChunk.bind(this),
@@ -144,22 +152,22 @@
             return target.parentNode.scrollHeight;
           }, opt_clipToTop);
       this._fixSide();
-    },
+    }
 
     moveToPreviousChunk() {
       this.$.cursorManager.previous(this._isFirstRowOfChunk.bind(this));
       this._fixSide();
-    },
+    }
 
     moveToNextCommentThread() {
       this.$.cursorManager.next(this._rowHasThread.bind(this));
       this._fixSide();
-    },
+    }
 
     moveToPreviousCommentThread() {
       this.$.cursorManager.previous(this._rowHasThread.bind(this));
       this._fixSide();
-    },
+    }
 
     /**
      * @param {number} number
@@ -172,7 +180,7 @@
         this.side = side;
         this.$.cursorManager.setCursor(row);
       }
-    },
+    }
 
     /**
      * Get the line number element targeted by the cursor row and side.
@@ -190,7 +198,7 @@
       }
 
       return this.diffRow.querySelector(lineElSelector);
-    },
+    }
 
     getTargetDiffElement() {
       if (!this.diffRow) return null;
@@ -202,12 +210,12 @@
         return hostOwner.host;
       }
       return null;
-    },
+    }
 
     moveToFirstChunk() {
       this.$.cursorManager.moveToStart();
       this.moveToNextChunk(true);
-    },
+    }
 
     reInitCursor() {
       this._updateStops();
@@ -217,7 +225,7 @@
       } else {
         this.moveToFirstChunk();
       }
-    },
+    }
 
     _handleWindowScroll() {
       if (this._listeningForScroll) {
@@ -225,7 +233,7 @@
         this._focusOnMove = false;
         this._listeningForScroll = false;
       }
-    },
+    }
 
     handleDiffUpdate() {
       this._updateStops();
@@ -240,11 +248,11 @@
       this._scrollBehavior = ScrollBehavior.KEEP_VISIBLE;
       this._focusOnMove = true;
       this._listeningForScroll = false;
-    },
+    }
 
     _handleDiffRenderStart() {
       this._listeningForScroll = true;
-    },
+    }
 
     createCommentInPlace() {
       const diffWithRangeSelected = this.diffs.find(diff => {
@@ -258,7 +266,7 @@
           this.getTargetDiffElement().addDraftAtLine(line);
         }
       }
-    },
+    }
 
     /**
      * Get an object describing the location of the cursor. Such as
@@ -290,7 +298,7 @@
         leftSide: cell.matches('.left'),
         number: parseInt(number, 10),
       };
-    },
+    }
 
     _getViewMode() {
       if (!this.diffRow) {
@@ -302,24 +310,24 @@
       } else {
         return DiffViewMode.UNIFIED;
       }
-    },
+    }
 
     _rowHasSide(row) {
       const selector = (this.side === DiffSides.LEFT ? '.left' : '.right') +
           ' + .content';
       return !!row.querySelector(selector);
-    },
+    }
 
     _isFirstRowOfChunk(row) {
       const parentClassList = row.parentNode.classList;
       return parentClassList.contains('section') &&
           parentClassList.contains('delta') &&
           !row.previousSibling;
-    },
+    }
 
     _rowHasThread(row) {
       return row.querySelector('.thread-group');
-    },
+    }
 
     /**
      * If we jumped to a row where there is no content on the current side then
@@ -331,7 +339,7 @@
         this.side = this.side === DiffSides.LEFT ?
           DiffSides.RIGHT : DiffSides.LEFT;
       }
-    },
+    }
 
     _isTargetBlank() {
       if (!this.diffRow) {
@@ -341,14 +349,14 @@
       const actions = this._getActionsForRow();
       return (this.side === DiffSides.LEFT && !actions.left) ||
           (this.side === DiffSides.RIGHT && !actions.right);
-    },
+    }
 
     _rowChanged(newRow, oldRow) {
       if (oldRow) {
         oldRow.classList.remove(LEFT_SIDE_CLASS, RIGHT_SIDE_CLASS);
       }
       this._updateSideClass();
-    },
+    }
 
     _updateSideClass() {
       if (!this.diffRow) {
@@ -358,11 +366,11 @@
           this.diffRow);
       this.toggleClass(RIGHT_SIDE_CLASS, this.side === DiffSides.RIGHT,
           this.diffRow);
-    },
+    }
 
     _isActionType(type) {
       return type !== 'blank' && type !== 'contextControl';
-    },
+    }
 
     _getActionsForRow() {
       const actions = {left: false, right: false};
@@ -373,18 +381,18 @@
             this.diffRow.getAttribute('right-type'));
       }
       return actions;
-    },
+    }
 
     _getStops() {
       return this.diffs.reduce(
           (stops, diff) => {
             return stops.concat(diff.getCursorStops());
           }, []);
-    },
+    }
 
     _updateStops() {
       this.$.cursorManager.stops = this._getStops();
-    },
+    }
 
     /**
      * Setup and tear down on-render listeners for any diffs that are added or
@@ -420,7 +428,7 @@
               'render-content', 'handleDiffUpdate');
         }
       }
-    },
+    }
 
     _findRowByNumberAndFile(targetNumber, side, opt_path) {
       let stops;
@@ -437,6 +445,8 @@
           return stops[i];
         }
       }
-    },
-  });
+    }
+  }
+
+  customElements.define(GrDiffCursor.is, GrDiffCursor);
 })();
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-cursor/gr-diff-cursor_test.html b/polygerrit-ui/app/elements/diff/gr-diff-cursor/gr-diff-cursor_test.html
index 0438287..626ab33 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-cursor/gr-diff-cursor_test.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff-cursor/gr-diff-cursor_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-diff-cursor</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>
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-highlight/gr-annotation_test.html b/polygerrit-ui/app/elements/diff/gr-diff-highlight/gr-annotation_test.html
index e3724fc..a032bc2 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-highlight/gr-annotation_test.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff-highlight/gr-annotation_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-annotation</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>
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-highlight/gr-diff-highlight.js b/polygerrit-ui/app/elements/diff/gr-diff-highlight/gr-diff-highlight.js
index 4ccc8f2..88d19aa 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-highlight/gr-diff-highlight.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff-highlight/gr-diff-highlight.js
@@ -17,24 +17,32 @@
 (function() {
   'use strict';
 
-  Polymer({
-    is: 'gr-diff-highlight',
+  /**
+    * @appliesMixin Gerrit.FireMixin
+    */
+  class GrDiffHighlight extends Polymer.mixinBehaviors( [
+    Gerrit.FireBehavior,
+  ], Polymer.GestureEventListeners(
+      Polymer.LegacyElementMixin(
+          Polymer.Element))) {
+    static get is() { return 'gr-diff-highlight'; }
 
-    properties: {
+    static get properties() {
+      return {
       /** @type {!Array<!Gerrit.HoveredRange>} */
-      commentRanges: {
-        type: Array,
-        notify: true,
-      },
-      loggedIn: Boolean,
-      /**
+        commentRanges: {
+          type: Array,
+          notify: true,
+        },
+        loggedIn: Boolean,
+        /**
        * querySelector can return null, so needs to be nullable.
        *
        * @type {?HTMLElement}
        * */
-      _cachedDiffBuilder: Object,
+        _cachedDiffBuilder: Object,
 
-      /**
+        /**
        * Which range is currently selected by the user.
        * Stored in order to add a range-based comment
        * later.
@@ -42,21 +50,22 @@
        *
        * @type {{side: string, range: Gerrit.Range}|undefined}
        */
-      selectedRange: {
-        type: Object,
-        notify: true,
-      },
-    },
+        selectedRange: {
+          type: Object,
+          notify: true,
+        },
+      };
+    }
 
-    behaviors: [
-      Gerrit.FireBehavior,
-    ],
-
-    listeners: {
-      'comment-thread-mouseleave': '_handleCommentThreadMouseleave',
-      'comment-thread-mouseenter': '_handleCommentThreadMouseenter',
-      'create-comment-requested': '_handleRangeCommentRequest',
-    },
+    created() {
+      super.created();
+      this.addEventListener('comment-thread-mouseleave',
+          e => this._handleCommentThreadMouseleave(e));
+      this.addEventListener('comment-thread-mouseenter',
+          e => this._handleCommentThreadMouseenter(e));
+      this.addEventListener('create-comment-requested',
+          e => this._handleRangeCommentRequest(e));
+    }
 
     get diffBuilder() {
       if (!this._cachedDiffBuilder) {
@@ -64,7 +73,7 @@
             Polymer.dom(this).querySelector('gr-diff-builder');
       }
       return this._cachedDiffBuilder;
-    },
+    }
 
     /**
      * Determines side/line/range for a DOM selection and shows a tooltip.
@@ -93,7 +102,7 @@
       this.debounce(
           'selectionChange', () => this._handleSelection(selection, isMouseUp),
           10);
-    },
+    }
 
     _getThreadEl(e) {
       const path = Polymer.dom(e).path || [];
@@ -101,7 +110,7 @@
         if (pathEl.classList.contains('comment-thread')) return pathEl;
       }
       return null;
-    },
+    }
 
     _handleCommentThreadMouseenter(e) {
       const threadEl = this._getThreadEl(e);
@@ -110,7 +119,7 @@
       if (index !== undefined) {
         this.set(['commentRanges', index, 'hovering'], true);
       }
-    },
+    }
 
     _handleCommentThreadMouseleave(e) {
       const threadEl = this._getThreadEl(e);
@@ -119,7 +128,7 @@
       if (index !== undefined) {
         this.set(['commentRanges', index, 'hovering'], false);
       }
-    },
+    }
 
     _indexForThreadEl(threadEl) {
       const side = threadEl.getAttribute('comment-side');
@@ -128,7 +137,7 @@
       if (!range) return undefined;
 
       return this._indexOfCommentRange(side, range);
-    },
+    }
 
     _indexOfCommentRange(side, range) {
       function rangesEqual(a, b) {
@@ -146,7 +155,7 @@
 
       return this.commentRanges.findIndex(commentRange =>
         commentRange.side === side && rangesEqual(commentRange.range, range));
-    },
+    }
 
     /**
      * Get current normalized selection.
@@ -184,7 +193,7 @@
           end: endRange.end,
         };
       }
-    },
+    }
 
     /**
      * Normalize a specific DOM Range.
@@ -198,7 +207,7 @@
         end: this._normalizeSelectionSide(
             range.endContainer, range.endOffset),
       }, domRange);
-    },
+    }
 
     /**
      * Adjust triple click selection for the whole line.
@@ -239,7 +248,7 @@
         };
       }
       return range;
-    },
+    }
 
     /**
      * Convert DOM Range selection to concrete numbers (line, column, side).
@@ -296,7 +305,7 @@
         line,
         column,
       };
-    },
+    }
 
     /**
      * The only line in which add a comment tooltip is cut off is the first
@@ -312,7 +321,7 @@
       }
       actionBox.positionBelow = true;
       actionBox.placeBelow(range);
-    },
+    }
 
     _isRangeValid(range) {
       if (!range || !range.start || !range.end) {
@@ -326,7 +335,7 @@
         return false;
       }
       return true;
-    },
+    }
 
     _handleSelection(selection, isMouseUp) {
       const normalizedRange = this._getNormalizedRange(selection);
@@ -398,12 +407,12 @@
       } else {
         this._positionActionBox(actionBox, start.line, start.node);
       }
-    },
+    }
 
     _fireCreateRangeComment(side, range) {
       this.fire('create-range-comment', {side, range});
       this._removeActionBox();
-    },
+    }
 
     _handleRangeCommentRequest(e) {
       e.stopPropagation();
@@ -412,7 +421,7 @@
       }
       const {side, range} = this.selectedRange;
       this._fireCreateRangeComment(side, range);
-    },
+    }
 
     _removeActionBox() {
       this.selectedRange = undefined;
@@ -420,7 +429,7 @@
       if (actionBox) {
         Polymer.dom(this.root).removeChild(actionBox);
       }
-    },
+    }
 
     _convertOffsetToColumn(el, offset) {
       if (el instanceof Element && el.classList.contains('content')) {
@@ -436,7 +445,7 @@
         }
       }
       return offset;
-    },
+    }
 
     /**
      * Traverse Element from right to left, call callback for each node.
@@ -461,7 +470,7 @@
         }
         node = nextNode;
       }
-    },
+    }
 
     /**
      * Get length of a node. If the node is a content node, then only give the
@@ -476,6 +485,8 @@
       } else {
         return GrAnnotation.getLength(node);
       }
-    },
-  });
+    }
+  }
+
+  customElements.define(GrDiffHighlight.is, GrDiffHighlight);
 })();
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-highlight/gr-diff-highlight_test.html b/polygerrit-ui/app/elements/diff/gr-diff-highlight/gr-diff-highlight_test.html
index aca7799..3de521d 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-highlight/gr-diff-highlight_test.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff-highlight/gr-diff-highlight_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-diff-highlight</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>
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-host/gr-diff-host.js b/polygerrit-ui/app/elements/diff/gr-diff-host/gr-diff-host.js
index 5d1709c..f5dc438 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-host/gr-diff-host.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff-host/gr-diff-host.js
@@ -69,14 +69,22 @@
   };
 
   /**
+    * @appliesMixin Gerrit.FireMixin
+    * @appliesMixin Gerrit.PatchSetMixin
+    */
+  /**
    * Wrapper around gr-diff.
    *
    * Webcomponent fetching diffs and related data from restAPI and passing them
    * to the presentational gr-diff for rendering.
    */
-  Polymer({
-    is: 'gr-diff-host',
-
+  class GrDiffHost extends Polymer.mixinBehaviors( [
+    Gerrit.FireBehavior,
+    Gerrit.PatchSetBehavior,
+  ], Polymer.GestureEventListeners(
+      Polymer.LegacyElementMixin(
+          Polymer.Element))) {
+    static get is() { return 'gr-diff-host'; }
     /**
      * Fired when the user selects a line.
      * @event line-selected
@@ -94,182 +102,191 @@
      * @event diff-comments-modified
      */
 
-    properties: {
-      changeNum: String,
-      noAutoRender: {
-        type: Boolean,
-        value: false,
-      },
-      /** @type {?} */
-      patchRange: Object,
-      path: String,
-      prefs: {
-        type: Object,
-      },
-      projectName: String,
-      displayLine: {
-        type: Boolean,
-        value: false,
-      },
-      isImageDiff: {
-        type: Boolean,
-        computed: '_computeIsImageDiff(diff)',
-        notify: true,
-      },
-      commitRange: Object,
-      filesWeblinks: {
-        type: Object,
-        value() {
-          return {};
+    static get properties() {
+      return {
+        changeNum: String,
+        noAutoRender: {
+          type: Boolean,
+          value: false,
         },
-        notify: true,
-      },
-      hidden: {
-        type: Boolean,
-        reflectToAttribute: true,
-      },
-      noRenderOnPrefsChange: {
-        type: Boolean,
-        value: false,
-      },
-      comments: {
-        type: Object,
-        observer: '_commentsChanged',
-      },
-      lineWrapping: {
-        type: Boolean,
-        value: false,
-      },
-      viewMode: {
-        type: String,
-        value: DiffViewMode.SIDE_BY_SIDE,
-      },
+        /** @type {?} */
+        patchRange: Object,
+        path: String,
+        prefs: {
+          type: Object,
+        },
+        projectName: String,
+        displayLine: {
+          type: Boolean,
+          value: false,
+        },
+        isImageDiff: {
+          type: Boolean,
+          computed: '_computeIsImageDiff(diff)',
+          notify: true,
+        },
+        commitRange: Object,
+        filesWeblinks: {
+          type: Object,
+          value() {
+            return {};
+          },
+          notify: true,
+        },
+        hidden: {
+          type: Boolean,
+          reflectToAttribute: true,
+        },
+        noRenderOnPrefsChange: {
+          type: Boolean,
+          value: false,
+        },
+        comments: {
+          type: Object,
+          observer: '_commentsChanged',
+        },
+        lineWrapping: {
+          type: Boolean,
+          value: false,
+        },
+        viewMode: {
+          type: String,
+          value: DiffViewMode.SIDE_BY_SIDE,
+        },
 
-      /**
+        /**
        * Special line number which should not be collapsed into a shared region.
        * @type {{
        *  number: number,
        *  leftSide: {boolean}
        * }|null}
        */
-      lineOfInterest: Object,
+        lineOfInterest: Object,
 
-      /**
+        /**
        * If the diff fails to load, show the failure message in the diff rather
        * than bubbling the error up to the whole page. This is useful for when
        * loading inline diffs because one diff failing need not mark the whole
        * page with a failure.
        */
-      showLoadFailure: Boolean,
+        showLoadFailure: Boolean,
 
-      isBlameLoaded: {
-        type: Boolean,
-        notify: true,
-        computed: '_computeIsBlameLoaded(_blame)',
-      },
+        isBlameLoaded: {
+          type: Boolean,
+          notify: true,
+          computed: '_computeIsBlameLoaded(_blame)',
+        },
 
-      _loggedIn: {
-        type: Boolean,
-        value: false,
-      },
+        _loggedIn: {
+          type: Boolean,
+          value: false,
+        },
 
-      _loading: {
-        type: Boolean,
-        value: false,
-      },
+        _loading: {
+          type: Boolean,
+          value: false,
+        },
 
-      /** @type {?string} */
-      _errorMessage: {
-        type: String,
-        value: null,
-      },
+        /** @type {?string} */
+        _errorMessage: {
+          type: String,
+          value: null,
+        },
 
-      /** @type {?Object} */
-      _baseImage: Object,
-      /** @type {?Object} */
-      _revisionImage: Object,
-      /**
+        /** @type {?Object} */
+        _baseImage: Object,
+        /** @type {?Object} */
+        _revisionImage: Object,
+        /**
        * This is a DiffInfo object.
        */
-      diff: {
-        type: Object,
-        notify: true,
-      },
+        diff: {
+          type: Object,
+          notify: true,
+        },
 
-      /** @type {?Object} */
-      _blame: {
-        type: Object,
-        value: null,
-      },
+        /** @type {?Object} */
+        _blame: {
+          type: Object,
+          value: null,
+        },
 
-      /**
+        /**
        * @type {!Array<!Gerrit.CoverageRange>}
        */
-      _coverageRanges: {
-        type: Array,
-        value: () => [],
-      },
+        _coverageRanges: {
+          type: Array,
+          value: () => [],
+        },
 
-      _loadedWhitespaceLevel: String,
+        _loadedWhitespaceLevel: String,
 
-      _parentIndex: {
-        type: Number,
-        computed: '_computeParentIndex(patchRange.*)',
-      },
+        _parentIndex: {
+          type: Number,
+          computed: '_computeParentIndex(patchRange.*)',
+        },
 
-      _syntaxHighlightingEnabled: {
-        type: Boolean,
-        computed:
+        _syntaxHighlightingEnabled: {
+          type: Boolean,
+          computed:
           '_isSyntaxHighlightingEnabled(prefs.*, diff)',
-      },
+        },
 
-      _layers: {
-        type: Array,
-        value: [],
-      },
-    },
+        _layers: {
+          type: Array,
+          value: [],
+        },
+      };
+    }
 
-    behaviors: [
-      Gerrit.FireBehavior,
-      Gerrit.PatchSetBehavior,
-    ],
-
-    listeners: {
-      // These are named inconsistently for a reason:
-      // The create-comment event is fired to indicate that we should
-      // create a comment.
-      // The comment-* events are just notifying that the comments did already
-      // change in some way, and that we should update any models we may want
-      // to keep in sync.
-      'create-comment': '_handleCreateComment',
-      'comment-discard': '_handleCommentDiscard',
-      'comment-update': '_handleCommentUpdate',
-      'comment-save': '_handleCommentSave',
-
-      'render-start': '_handleRenderStart',
-      'render-content': '_handleRenderContent',
-
-      'normalize-range': '_handleNormalizeRange',
-      'diff-context-expanded': '_handleDiffContextExpanded',
-    },
-
-    observers: [
-      '_whitespaceChanged(prefs.ignore_whitespace, _loadedWhitespaceLevel,' +
+    static get observers() {
+      return [
+        '_whitespaceChanged(prefs.ignore_whitespace, _loadedWhitespaceLevel,' +
           ' noRenderOnPrefsChange)',
-      '_syntaxHighlightingChanged(noRenderOnPrefsChange, prefs.*)',
-    ],
+        '_syntaxHighlightingChanged(noRenderOnPrefsChange, prefs.*)',
+      ];
+    }
+
+    created() {
+      super.created();
+      this.addEventListener(
+          // These are named inconsistently for a reason:
+          // The create-comment event is fired to indicate that we should
+          // create a comment.
+          // The comment-* events are just notifying that the comments did already
+          // change in some way, and that we should update any models we may want
+          // to keep in sync.
+          'create-comment',
+          e => this._handleCreateComment(e));
+      this.addEventListener('comment-discard',
+          e => this._handleCommentDiscard(e));
+      this.addEventListener('comment-update',
+          e => this._handleCommentUpdate(e));
+      this.addEventListener('comment-save',
+          e => this._handleCommentSave(e));
+      this.addEventListener('render-start',
+          () => this._handleRenderStart());
+      this.addEventListener('render-content',
+          () => this._handleRenderContent());
+      this.addEventListener('normalize-range',
+          event => this._handleNormalizeRange(event));
+      this.addEventListener('diff-context-expanded',
+          event => this._handleDiffContextExpanded(event));
+    }
 
     ready() {
+      super.ready();
       if (this._canReload()) {
         this.reload();
       }
-    },
+    }
 
     attached() {
+      super.attached();
       this._getLoggedIn().then(loggedIn => {
         this._loggedIn = loggedIn;
       });
-    },
+    }
 
     /**
      * @param {boolean=} shouldReportMetric indicate a new Diff Page. This is a
@@ -364,7 +381,7 @@
             console.warn('Error encountered loading diff:', err);
           })
           .then(() => { this._loading = false; });
-    },
+    }
 
     _getFilesWeblinks(diff) {
       if (!this.commitRange) {
@@ -378,30 +395,30 @@
             this.projectName, this.commitRange.commit, this.path,
             {weblinks: diff && diff.meta_b && diff.meta_b.web_links}),
       };
-    },
+    }
 
     /** Cancel any remaining diff builder rendering work. */
     cancel() {
       this.$.diff.cancel();
-    },
+    }
 
     /** @return {!Array<!HTMLElement>} */
     getCursorStops() {
       return this.$.diff.getCursorStops();
-    },
+    }
 
     /** @return {boolean} */
     isRangeSelected() {
       return this.$.diff.isRangeSelected();
-    },
+    }
 
     createRangeComment() {
       return this.$.diff.createRangeComment();
-    },
+    }
 
     toggleLeftDiff() {
       this.$.diff.toggleLeftDiff();
-    },
+    }
 
     /**
      * Load and display blame information for the base of the diff.
@@ -418,12 +435,12 @@
 
             this._blame = blame;
           });
-    },
+    }
 
     /** Unload blame information for the diff. */
     clearBlame() {
       this._blame = null;
-    },
+    }
 
     /**
      * The thread elements in this diff, in no particular order.
@@ -432,31 +449,31 @@
     getThreadEls() {
       return Array.from(
           Polymer.dom(this.$.diff).querySelectorAll('.comment-thread'));
-    },
+    }
 
     /** @param {HTMLElement} el */
     addDraftAtLine(el) {
       this.$.diff.addDraftAtLine(el);
-    },
+    }
 
     clearDiffContent() {
       this.$.diff.clearDiffContent();
-    },
+    }
 
     expandAllContext() {
       this.$.diff.expandAllContext();
-    },
+    }
 
     /** @return {!Promise} */
     _getLoggedIn() {
       return this.$.restAPI.getLoggedIn();
-    },
+    }
 
     /** @return {boolean}} */
     _canReload() {
       return !!this.changeNum && !!this.patchRange && !!this.path &&
           !this.noAutoRender;
-    },
+    }
 
     /** @return {!Promise<!Object>} */
     _getDiff() {
@@ -472,7 +489,7 @@
             reject)
             .then(resolve);
       });
-    },
+    }
 
     _handleGetDiffError(response) {
       // Loading the diff may respond with 409 if the file is too large. In this
@@ -492,7 +509,7 @@
       }
 
       this.fire('page-error', {response});
-    },
+    }
 
     /**
      * Report info about the diff response.
@@ -533,7 +550,7 @@
         this.$.reporting.reportInteraction(EVENT_NONZERO_REBASE,
             percentRebaseDelta);
       }
-    },
+    }
 
     /**
      * @param {Object} diff
@@ -550,7 +567,7 @@
         this._revisionImage = null;
         return Promise.resolve();
       }
-    },
+    }
 
     /**
      * @param {Object} diff
@@ -558,7 +575,7 @@
      */
     _computeIsImageDiff(diff) {
       return isImageDiff(diff);
-    },
+    }
 
     _commentsChanged(newComments) {
       const allComments = [];
@@ -579,7 +596,7 @@
         const threadEl = this._createThreadElement(thread);
         this._attachThreadElement(threadEl);
       }
-    },
+    }
 
     /**
      * @param {!Array<!Object>} comments
@@ -621,7 +638,7 @@
         threads.push(newThread);
       }
       return threads;
-    },
+    }
 
     /**
      * @param {Object} blame
@@ -629,7 +646,7 @@
      */
     _computeIsBlameLoaded(blame) {
       return !!blame;
-    },
+    }
 
     /**
      * @param {Object} diff
@@ -638,7 +655,7 @@
     _getImages(diff) {
       return this.$.restAPI.getImagesForDiff(this.changeNum, diff,
           this.patchRange);
-    },
+    }
 
     /** @param {CustomEvent} e */
     _handleCreateComment(e) {
@@ -648,7 +665,7 @@
       threadEl.addOrEditDraft(lineNum, range);
 
       this.$.reporting.recordDraftInteraction();
-    },
+    }
 
     /**
      * Gets or creates a comment thread at a given location.
@@ -675,18 +692,18 @@
         this._attachThreadElement(threadEl);
       }
       return threadEl;
-    },
+    }
 
     _attachThreadElement(threadEl) {
       Polymer.dom(this.$.diff).appendChild(threadEl);
-    },
+    }
 
     _clearThreads() {
       for (const threadEl of this.getThreadEls()) {
         const parent = Polymer.dom(threadEl).parentNode;
         Polymer.dom(parent).removeChild(threadEl);
       }
-    },
+    }
 
     _createThreadElement(thread) {
       const threadEl = document.createElement('gr-comment-thread');
@@ -717,7 +734,7 @@
       };
       threadEl.addEventListener('thread-discard', threadDiscardListener);
       return threadEl;
-    },
+    }
 
     /**
      * Gets a comment thread element at a given location.
@@ -746,7 +763,7 @@
       const filteredThreadEls = this._filterThreadElsForLocation(
           this.getThreadEls(), line, commentSide).filter(matchesRange);
       return filteredThreadEls.length ? filteredThreadEls[0] : null;
-    },
+    }
 
     /**
      * @param {!Array<!HTMLElement>} threadEls
@@ -791,14 +808,14 @@
       }
       return threadEls.filter(threadEl =>
         matchers.some(matcher => matcher(threadEl)));
-    },
+    }
 
     _getIgnoreWhitespace() {
       if (!this.prefs || !this.prefs.ignore_whitespace) {
         return WHITESPACE_IGNORE_NONE;
       }
       return this.prefs.ignore_whitespace;
-    },
+    }
 
     _whitespaceChanged(
         preferredWhitespaceLevel, loadedWhitespaceLevel,
@@ -816,7 +833,7 @@
           !noRenderOnPrefsChange) {
         this.reload();
       }
-    },
+    }
 
     _syntaxHighlightingChanged(noRenderOnPrefsChange, prefsChangeRecord) {
       // Polymer 2: check for undefined
@@ -834,7 +851,7 @@
       if (!noRenderOnPrefsChange) {
         this.reload();
       }
-    },
+    }
 
     /**
      * @param {Object} patchRangeRecord
@@ -843,7 +860,7 @@
     _computeParentIndex(patchRangeRecord) {
       return this.isMergeParent(patchRangeRecord.base.basePatchNum) ?
         this.getParentIndex(patchRangeRecord.base.basePatchNum) : null;
-    },
+    }
 
     _handleCommentSave(e) {
       const comment = e.detail.comment;
@@ -851,13 +868,13 @@
       const idx = this._findDraftIndex(comment, side);
       this.set(['comments', side, idx], comment);
       this._handleCommentSaveOrDiscard();
-    },
+    }
 
     _handleCommentDiscard(e) {
       const comment = e.detail.comment;
       this._removeComment(comment);
       this._handleCommentSaveOrDiscard();
-    },
+    }
 
     /**
      * Closure annotation for Polymer.prototype.push is off. Submitted PR:
@@ -878,17 +895,17 @@
       } else { // Create new draft.
         this.push(['comments', side], comment);
       }
-    },
+    }
 
     _handleCommentSaveOrDiscard() {
       this.dispatchEvent(new CustomEvent(
           'diff-comments-modified', {bubbles: true, composed: true}));
-    },
+    }
 
     _removeComment(comment) {
       const side = comment.__commentSide;
       this._removeCommentFromSide(comment, side);
-    },
+    }
 
     _removeCommentFromSide(comment, side) {
       let idx = this._findCommentIndex(comment, side);
@@ -898,7 +915,7 @@
       if (idx !== -1) {
         this.splice('comments.' + side, idx, 1);
       }
-    },
+    }
 
     /** @return {number} */
     _findCommentIndex(comment, side) {
@@ -906,7 +923,7 @@
         return -1;
       }
       return this.comments[side].findIndex(item => item.id === comment.id);
-    },
+    }
 
     /** @return {number} */
     _findDraftIndex(comment, side) {
@@ -915,7 +932,7 @@
       }
       return this.comments[side].findIndex(
           item => item.__draftID === comment.__draftID);
-    },
+    }
 
     _isSyntaxHighlightingEnabled(preferenceChangeRecord, diff) {
       if (!preferenceChangeRecord ||
@@ -926,7 +943,7 @@
       }
       return !this._anyLineTooLong(diff) &&
           this.$.diff.getDiffLength(diff) <= SYNTAX_MAX_DIFF_LENGTH;
-    },
+    }
 
     /**
      * @return {boolean} whether any of the lines in diff are longer
@@ -940,7 +957,7 @@
           (section.a || []).concat(section.b || []);
         return lines.some(line => line.length >= SYNTAX_MAX_LINE_LENGTH);
       });
-    },
+    }
 
     _listenToViewportRender() {
       const renderUpdateListener = start => {
@@ -951,26 +968,28 @@
       };
 
       this.$.syntaxLayer.addListener(renderUpdateListener);
-    },
+    }
 
     _handleRenderStart() {
       this.$.reporting.time(TimingLabel.TOTAL);
       this.$.reporting.time(TimingLabel.CONTENT);
-    },
+    }
 
     _handleRenderContent() {
       this.$.reporting.timeEnd(TimingLabel.CONTENT);
-    },
+    }
 
     _handleNormalizeRange(event) {
       this.$.reporting.reportInteraction('normalize-range',
           `Modified invalid comment range on l. ${event.detail.lineNum}` +
           ` of the ${event.detail.side} side`);
-    },
+    }
 
     _handleDiffContextExpanded(event) {
       this.$.reporting.reportInteraction(
           'diff-context-expanded', event.detail.numLines);
-    },
-  });
+    }
+  }
+
+  customElements.define(GrDiffHost.is, GrDiffHost);
 })();
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-host/gr-diff-host_test.html b/polygerrit-ui/app/elements/diff/gr-diff-host/gr-diff-host_test.html
index eb0a68d..ea94441 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-host/gr-diff-host_test.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff-host/gr-diff-host_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-diff</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>
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-mode-selector/gr-diff-mode-selector.js b/polygerrit-ui/app/elements/diff/gr-diff-mode-selector/gr-diff-mode-selector.js
index 88dd91a..8d8785a 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-mode-selector/gr-diff-mode-selector.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff-mode-selector/gr-diff-mode-selector.js
@@ -17,34 +17,38 @@
 (function() {
   'use strict';
 
-  Polymer({
-    is: 'gr-diff-mode-selector',
+  class GrDiffModeSelector extends Polymer.GestureEventListeners(
+      Polymer.LegacyElementMixin(
+          Polymer.Element)) {
+    static get is() { return 'gr-diff-mode-selector'; }
 
-    properties: {
-      mode: {
-        type: String,
-        notify: true,
-      },
+    static get properties() {
+      return {
+        mode: {
+          type: String,
+          notify: true,
+        },
 
-      /**
+        /**
        * If set to true, the user's preference will be updated every time a
        * button is tapped. Don't set to true if there is no user.
        */
-      saveOnChange: {
-        type: Boolean,
-        value: false,
-      },
-
-      /** @type {?} */
-      _VIEW_MODES: {
-        type: Object,
-        readOnly: true,
-        value: {
-          SIDE_BY_SIDE: 'SIDE_BY_SIDE',
-          UNIFIED: 'UNIFIED_DIFF',
+        saveOnChange: {
+          type: Boolean,
+          value: false,
         },
-      },
-    },
+
+        /** @type {?} */
+        _VIEW_MODES: {
+          type: Object,
+          readOnly: true,
+          value: {
+            SIDE_BY_SIDE: 'SIDE_BY_SIDE',
+            UNIFIED: 'UNIFIED_DIFF',
+          },
+        },
+      };
+    }
 
     /**
      * Set the mode. If save on change is enabled also update the preference.
@@ -54,18 +58,20 @@
         this.$.restAPI.savePreferences({diff_view: newMode});
       }
       this.mode = newMode;
-    },
+    }
 
     _computeSelectedClass(diffViewMode, buttonViewMode) {
       return buttonViewMode === diffViewMode ? 'selected' : '';
-    },
+    }
 
     _handleSideBySideTap() {
       this.setMode(this._VIEW_MODES.SIDE_BY_SIDE);
-    },
+    }
 
     _handleUnifiedTap() {
       this.setMode(this._VIEW_MODES.UNIFIED);
-    },
-  });
+    }
+  }
+
+  customElements.define(GrDiffModeSelector.is, GrDiffModeSelector);
 })();
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-mode-selector/gr-diff-mode-selector_test.html b/polygerrit-ui/app/elements/diff/gr-diff-mode-selector/gr-diff-mode-selector_test.html
index adeaa15..333a3d6 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-mode-selector/gr-diff-mode-selector_test.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff-mode-selector/gr-diff-mode-selector_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-diff-mode-selector</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>
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-preferences-dialog/gr-diff-preferences-dialog.js b/polygerrit-ui/app/elements/diff/gr-diff-preferences-dialog/gr-diff-preferences-dialog.js
index 6ecd4d6..2d79311 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-preferences-dialog/gr-diff-preferences-dialog.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff-preferences-dialog/gr-diff-preferences-dialog.js
@@ -17,39 +17,44 @@
 (function() {
   'use strict';
 
-  Polymer({
-    is: 'gr-diff-preferences-dialog',
+  /**
+    * @appliesMixin Gerrit.FireMixin
+    */
+  class GrDiffPreferencesDialog extends Polymer.mixinBehaviors( [
+    Gerrit.FireBehavior,
+  ], Polymer.GestureEventListeners(
+      Polymer.LegacyElementMixin(
+          Polymer.Element))) {
+    static get is() { return 'gr-diff-preferences-dialog'; }
 
-    properties: {
+    static get properties() {
+      return {
       /** @type {?} */
-      diffPrefs: Object,
+        diffPrefs: Object,
 
-      _diffPrefsChanged: Boolean,
-    },
-
-    behaviors: [
-      Gerrit.FireBehavior,
-    ],
+        _diffPrefsChanged: Boolean,
+      };
+    }
 
     getFocusStops() {
       return {
         start: this.$.diffPreferences.$.contextSelect,
         end: this.$.saveButton,
       };
-    },
+    }
 
     resetFocus() {
       this.$.diffPreferences.$.contextSelect.focus();
-    },
+    }
 
     _computeHeaderClass(changed) {
       return changed ? 'edited' : '';
-    },
+    }
 
     _handleCancelDiff(e) {
       e.stopPropagation();
       this.$.diffPrefsOverlay.close();
-    },
+    }
 
     open() {
       this.$.diffPrefsOverlay.open().then(() => {
@@ -57,7 +62,7 @@
         this.$.diffPrefsOverlay.setFocusStops(focusStops);
         this.resetFocus();
       });
-    },
+    }
 
     _handleSaveDiffPreferences() {
       this.$.diffPreferences.save().then(() => {
@@ -65,6 +70,8 @@
 
         this.$.diffPrefsOverlay.close();
       });
-    },
-  });
+    }
+  }
+
+  customElements.define(GrDiffPreferencesDialog.is, GrDiffPreferencesDialog);
 })();
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-processor/gr-diff-processor.js b/polygerrit-ui/app/elements/diff/gr-diff-processor/gr-diff-processor.js
index b67bde3..817da45 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-processor/gr-diff-processor.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff-processor/gr-diff-processor.js
@@ -64,71 +64,77 @@
    *    that the part that is within the context or has comments is shown, while
    *    the rest is not.
    */
-  Polymer({
-    is: 'gr-diff-processor',
+  class GrDiffProcessor extends Polymer.GestureEventListeners(
+      Polymer.LegacyElementMixin(
+          Polymer.Element)) {
+    static get is() { return 'gr-diff-processor'; }
 
-    properties: {
+    static get properties() {
+      return {
 
-      /**
+        /**
        * The amount of context around collapsed groups.
        */
-      context: Number,
+        context: Number,
 
-      /**
+        /**
        * The array of groups output by the processor.
        */
-      groups: {
-        type: Array,
-        notify: true,
-      },
+        groups: {
+          type: Array,
+          notify: true,
+        },
 
-      /**
+        /**
        * Locations that should not be collapsed, including the locations of
        * comments.
        */
-      keyLocations: {
-        type: Object,
-        value() { return {left: {}, right: {}}; },
-      },
+        keyLocations: {
+          type: Object,
+          value() { return {left: {}, right: {}}; },
+        },
 
-      /**
+        /**
        * The maximum number of lines to process synchronously.
        */
-      _asyncThreshold: {
-        type: Number,
-        value: 64,
-      },
+        _asyncThreshold: {
+          type: Number,
+          value: 64,
+        },
 
-      /** @type {?number} */
-      _nextStepHandle: Number,
-      /**
+        /** @type {?number} */
+        _nextStepHandle: Number,
+        /**
        * The promise last returned from `process()` while the asynchronous
        * processing is running - `null` otherwise. Provides a `cancel()`
        * method that rejects it with `{isCancelled: true}`.
        * @type {?Object}
        */
-      _processPromise: {
-        type: Object,
-        value: null,
-      },
-      _isScrolling: Boolean,
-    },
+        _processPromise: {
+          type: Object,
+          value: null,
+        },
+        _isScrolling: Boolean,
+      };
+    }
 
     attached() {
+      super.attached();
       this.listen(window, 'scroll', '_handleWindowScroll');
-    },
+    }
 
     detached() {
+      super.detached();
       this.cancel();
       this.unlisten(window, 'scroll', '_handleWindowScroll');
-    },
+    }
 
     _handleWindowScroll() {
       this._isScrolling = true;
       this.debounce('resetIsScrolling', () => {
         this._isScrolling = false;
       }, 50);
-    },
+    }
 
     /**
      * Asynchronously process the diff chunks into groups. As it processes, it
@@ -196,7 +202,7 @@
           }));
       return this._processPromise
           .finally(() => { this._processPromise = null; });
-    },
+    }
 
     /**
      * Cancel any jobs that are running.
@@ -209,7 +215,7 @@
       if (this._processPromise) {
         this._processPromise.cancel();
       }
-    },
+    }
 
     /**
      * Process the next uncollapsible chunk, or the next collapsible chunks.
@@ -236,15 +242,15 @@
 
       return this._processCollapsibleChunks(
           state, chunks, firstUncollapsibleChunkIndex);
-    },
+    }
 
     _linesLeft(chunk) {
       return chunk.ab || chunk.a || [];
-    },
+    }
 
     _linesRight(chunk) {
       return chunk.ab || chunk.b || [];
-    },
+    }
 
     _firstUncollapsibleChunkIndex(chunks, offset) {
       let chunkIndex = offset;
@@ -253,11 +259,11 @@
         chunkIndex++;
       }
       return chunkIndex;
-    },
+    }
 
     _isCollapsibleChunk(chunk) {
       return (chunk.ab || chunk.common) && !chunk.keyLocation;
-    },
+    }
 
     /**
      * Process a stretch of collapsible chunks.
@@ -303,7 +309,7 @@
         groups,
         newChunkIndex: firstUncollapsibleChunkIndex,
       };
-    },
+    }
 
     _commonChunkLength(chunk) {
       console.assert(chunk.ab || chunk.common);
@@ -311,7 +317,7 @@
           !chunk.a || (chunk.b && chunk.a.length === chunk.b.length),
           `common chunk needs same number of a and b lines: `, chunk);
       return this._linesLeft(chunk).length;
-    },
+    }
 
     /**
      * @param {!Array<!Object>} chunks
@@ -327,7 +333,7 @@
         offsetRight += chunkLength;
         return group;
       });
-    },
+    }
 
     /**
      * @param {!Object} chunk
@@ -343,7 +349,7 @@
       group.dueToRebase = chunk.due_to_rebase;
       group.ignoredWhitespaceOnly = chunk.common;
       return group;
-    },
+    }
 
     _linesFromChunk(chunk, offsetLeft, offsetRight) {
       if (chunk.ab) {
@@ -366,7 +372,7 @@
             chunk[DiffHighlights.ADDED]));
       }
       return lines;
-    },
+    }
 
     /**
      * @param {string} lineType (GrDiffLine.Type)
@@ -380,7 +386,7 @@
         this._convertIntralineInfos(rows, opt_intralineInfos) : undefined;
       return rows.map((row, i) => this._lineFromRow(
           lineType, offset, offset, row, i, grDiffHighlights));
-    },
+    }
 
     /**
      * @param {string} type (GrDiffLine.Type)
@@ -403,14 +409,14 @@
         line.hasIntralineInfo = false;
       }
       return line;
-    },
+    }
 
     _makeFileComments() {
       const line = new GrDiffLine(GrDiffLine.Type.BOTH);
       line.beforeNumber = GrDiffLine.FILE;
       line.afterNumber = GrDiffLine.FILE;
       return new GrDiffGroup(GrDiffGroup.Type.BOTH, [line]);
-    },
+    }
 
     /**
      * Split chunks into smaller chunks of the same kind.
@@ -452,7 +458,7 @@
         }
       }
       return newChunks;
-    },
+    }
 
     /**
      * In order to show key locations, such as comments, out of the bounds of
@@ -503,7 +509,7 @@
       }
 
       return result;
-    },
+    }
 
     /**
      * @return {!Array<{offset: number, keyLocation: boolean}>} Offsets of the
@@ -534,7 +540,7 @@
       }
 
       return result;
-    },
+    }
 
     _splitAtChunkEnds(lines, chunkEnds) {
       const result = [];
@@ -545,7 +551,7 @@
         lastChunkEndOffset = offset;
       }
       return result;
-    },
+    }
 
     /**
      * Converts `IntralineInfo`s return by the API to `GrLineHighlights` used
@@ -595,7 +601,7 @@
         normalized.push(lineHighlight);
       }
       return normalized;
-    },
+    }
 
     /**
      * If a group is an addition or a removal, break it down into smaller groups
@@ -625,7 +631,7 @@
             }
             return subChunk;
           });
-    },
+    }
 
     /**
      * Given an array and a size, return an array of arrays where no inner array
@@ -643,6 +649,8 @@
       const tail = array.slice(array.length - size);
 
       return this._breakdown(head, size).concat([tail]);
-    },
-  });
+    }
+  }
+
+  customElements.define(GrDiffProcessor.is, GrDiffProcessor);
 })();
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-processor/gr-diff-processor_test.html b/polygerrit-ui/app/elements/diff/gr-diff-processor/gr-diff-processor_test.html
index c04b066..308428f 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-processor/gr-diff-processor_test.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff-processor/gr-diff-processor_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-diff-processor test</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>
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-selection/gr-diff-selection.js b/polygerrit-ui/app/elements/diff/gr-diff-selection/gr-diff-selection.js
index c35c304..0dc0fba 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-selection/gr-diff-selection.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff-selection/gr-diff-selection.js
@@ -30,35 +30,46 @@
 
   const getNewCache = () => { return {left: null, right: null}; };
 
-  Polymer({
-    is: 'gr-diff-selection',
+  /**
+    * @appliesMixin Gerrit.DomUtilMixin
+    */
+  class GrDiffSelection extends Polymer.mixinBehaviors( [
+    Gerrit.DomUtilBehavior,
+  ], Polymer.GestureEventListeners(
+      Polymer.LegacyElementMixin(
+          Polymer.Element))) {
+    static get is() { return 'gr-diff-selection'; }
 
-    properties: {
-      diff: Object,
-      /** @type {?Object} */
-      _cachedDiffBuilder: Object,
-      _linesCache: {
-        type: Object,
-        value: getNewCache(),
-      },
-    },
+    static get properties() {
+      return {
+        diff: Object,
+        /** @type {?Object} */
+        _cachedDiffBuilder: Object,
+        _linesCache: {
+          type: Object,
+          value: getNewCache(),
+        },
+      };
+    }
 
-    observers: [
-      '_diffChanged(diff)',
-    ],
+    static get observers() {
+      return [
+        '_diffChanged(diff)',
+      ];
+    }
 
-    listeners: {
-      copy: '_handleCopy',
-      down: '_handleDown',
-    },
-
-    behaviors: [
-      Gerrit.DomUtilBehavior,
-    ],
+    created() {
+      super.created();
+      this.addEventListener('copy',
+          e => this._handleCopy(e));
+      Polymer.Gestures.addListener(this, 'down',
+          e => this._handleDown(e));
+    }
 
     attached() {
+      super.attached();
       this.classList.add(SelectionClass.RIGHT);
-    },
+    }
 
     get diffBuilder() {
       if (!this._cachedDiffBuilder) {
@@ -66,11 +77,11 @@
             Polymer.dom(this).querySelector('gr-diff-builder');
       }
       return this._cachedDiffBuilder;
-    },
+    }
 
     _diffChanged() {
       this._linesCache = getNewCache();
-    },
+    }
 
     _handleDownOnRangeComment(node) {
       if (node &&
@@ -85,7 +96,7 @@
         return true;
       }
       return false;
-    },
+    }
 
     _handleDown(e) {
       // Handle the down event on comment thread in Polymer 2
@@ -115,7 +126,7 @@
       }
 
       this._setClasses(targetClasses);
-    },
+    }
 
     /**
      * Set the provided list of classes on the element, to the exclusion of all
@@ -138,11 +149,11 @@
           this.classList.add(_class);
         }
       }
-    },
+    }
 
     _getCopyEventTarget(e) {
       return Polymer.dom(e).rootTarget;
-    },
+    }
 
     /**
      * Utility function to determine whether an element is a descendant of
@@ -155,7 +166,7 @@
     _elementDescendedFromClass(element, className) {
       return this.descendedFromClass(element, className,
           this.diffBuilder.diffElement);
-    },
+    }
 
     _handleCopy(e) {
       let commentSelected = false;
@@ -175,7 +186,7 @@
         e.clipboardData.setData('Text', text);
         e.preventDefault();
       }
-    },
+    }
 
     /**
      * For Polymer 2, use shadowRoot.getSelection instead.
@@ -186,7 +197,7 @@
         diffHost.shadowRoot &&
         diffHost.shadowRoot.getSelection();
       return selection ? selection: window.getSelection();
-    },
+    }
 
     /**
      * Get the text of the current selection. If commentSelected is
@@ -225,7 +236,7 @@
 
       return this._getRangeFromDiff(startLineNum, range.startOffset, endLineNum,
           range.endOffset, side);
-    },
+    }
 
     /**
      * Query the diff object for the selected lines.
@@ -247,7 +258,7 @@
         lines[0] = lines[0].substring(startOffset);
       }
       return lines.join('\n');
-    },
+    }
 
     /**
      * Query the diff object for the lines from a particular side.
@@ -270,7 +281,7 @@
       }
       this._linesCache[side] = lines;
       return lines;
-    },
+    }
 
     /**
      * Query the diffElement for comments and check whether they lie inside the
@@ -308,7 +319,7 @@
       }
 
       return content.join('\n');
-    },
+    }
 
     /**
      * Given a DOM node, a selection, and a selection range, recursively get all
@@ -338,6 +349,8 @@
         }
       }
       return text;
-    },
-  });
+    }
+  }
+
+  customElements.define(GrDiffSelection.is, GrDiffSelection);
 })();
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-selection/gr-diff-selection_test.html b/polygerrit-ui/app/elements/diff/gr-diff-selection/gr-diff-selection_test.html
index 9385658..6fe319d 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-selection/gr-diff-selection_test.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff-selection/gr-diff-selection_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-diff-selection</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>
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.js b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.js
index 1b413f7..4c401cf 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.js
@@ -33,9 +33,23 @@
     UNIFIED: 'UNIFIED_DIFF',
   };
 
-  Polymer({
-    is: 'gr-diff-view',
-
+  /**
+    * @appliesMixin Gerrit.FireMixin
+    * @appliesMixin Gerrit.KeyboardShortcutMixin
+    * @appliesMixin Gerrit.PatchSetMixin
+    * @appliesMixin Gerrit.PathListMixin
+    * @appliesMixin Gerrit.RESTClientMixin
+    */
+  class GrDiffView extends Polymer.mixinBehaviors( [
+    Gerrit.FireBehavior,
+    Gerrit.KeyboardShortcutBehavior,
+    Gerrit.PatchSetBehavior,
+    Gerrit.PathListBehavior,
+    Gerrit.RESTClientBehavior,
+  ], Polymer.GestureEventListeners(
+      Polymer.LegacyElementMixin(
+          Polymer.Element))) {
+    static get is() { return 'gr-diff-view'; }
     /**
      * Fired when the title of the page should change.
      *
@@ -48,154 +62,152 @@
      * @event show-alert
      */
 
-    properties: {
+    static get properties() {
+      return {
       /**
        * URL params passed from the router.
        */
-      params: {
-        type: Object,
-        observer: '_paramsChanged',
-      },
-      keyEventTarget: {
-        type: Object,
-        value() { return document.body; },
-      },
-      /**
+        params: {
+          type: Object,
+          observer: '_paramsChanged',
+        },
+        keyEventTarget: {
+          type: Object,
+          value() { return document.body; },
+        },
+        /**
        * @type {{ diffMode: (string|undefined) }}
        */
-      changeViewState: {
-        type: Object,
-        notify: true,
-        value() { return {}; },
-        observer: '_changeViewStateChanged',
-      },
-      disableDiffPrefs: {
-        type: Boolean,
-        value: false,
-      },
-      _diffPrefsDisabled: {
-        type: Boolean,
-        computed: '_computeDiffPrefsDisabled(disableDiffPrefs, _loggedIn)',
-      },
-      /** @type {?} */
-      _patchRange: Object,
-      /** @type {?} */
-      _commitRange: Object,
-      /**
+        changeViewState: {
+          type: Object,
+          notify: true,
+          value() { return {}; },
+          observer: '_changeViewStateChanged',
+        },
+        disableDiffPrefs: {
+          type: Boolean,
+          value: false,
+        },
+        _diffPrefsDisabled: {
+          type: Boolean,
+          computed: '_computeDiffPrefsDisabled(disableDiffPrefs, _loggedIn)',
+        },
+        /** @type {?} */
+        _patchRange: Object,
+        /** @type {?} */
+        _commitRange: Object,
+        /**
        * @type {{
        *  subject: string,
        *  project: string,
        *  revisions: string,
        * }}
        */
-      _change: Object,
-      /** @type {?} */
-      _changeComments: Object,
-      _changeNum: String,
-      /**
+        _change: Object,
+        /** @type {?} */
+        _changeComments: Object,
+        _changeNum: String,
+        /**
        * This is a DiffInfo object.
        * This is retrieved and owned by a child component.
        */
-      _diff: Object,
-      // An array specifically formatted to be used in a gr-dropdown-list
-      // element for selected a file to view.
-      _formattedFiles: {
-        type: Array,
-        computed: '_formatFilesForDropdown(_fileList, _patchRange.patchNum, ' +
-            '_changeComments)',
-      },
-      // An sorted array of files, as returned by the rest API.
-      _fileList: {
-        type: Array,
-        value() { return []; },
-      },
-      _path: {
-        type: String,
-        observer: '_pathChanged',
-      },
-      _fileNum: {
-        type: Number,
-        computed: '_computeFileNum(_path, _formattedFiles)',
-      },
-      _loggedIn: {
-        type: Boolean,
-        value: false,
-      },
-      _loading: {
-        type: Boolean,
-        value: true,
-      },
-      _prefs: Object,
-      _localPrefs: Object,
-      _projectConfig: Object,
-      _userPrefs: Object,
-      _diffMode: {
-        type: String,
-        computed: '_getDiffViewMode(changeViewState.diffMode, _userPrefs)',
-      },
-      _isImageDiff: Boolean,
-      _filesWeblinks: Object,
+        _diff: Object,
+        // An array specifically formatted to be used in a gr-dropdown-list
+        // element for selected a file to view.
+        _formattedFiles: {
+          type: Array,
+          computed: '_formatFilesForDropdown(_fileList, ' +
+            '_patchRange.patchNum, _changeComments)',
+        },
+        // An sorted array of files, as returned by the rest API.
+        _fileList: {
+          type: Array,
+          value() { return []; },
+        },
+        _path: {
+          type: String,
+          observer: '_pathChanged',
+        },
+        _fileNum: {
+          type: Number,
+          computed: '_computeFileNum(_path, _formattedFiles)',
+        },
+        _loggedIn: {
+          type: Boolean,
+          value: false,
+        },
+        _loading: {
+          type: Boolean,
+          value: true,
+        },
+        _prefs: Object,
+        _localPrefs: Object,
+        _projectConfig: Object,
+        _userPrefs: Object,
+        _diffMode: {
+          type: String,
+          computed: '_getDiffViewMode(changeViewState.diffMode, _userPrefs)',
+        },
+        _isImageDiff: Boolean,
+        _filesWeblinks: Object,
 
-      /**
+        /**
        * Map of paths in the current change and patch range that have comments
        * or drafts or robot comments.
        */
-      _commentMap: Object,
+        _commentMap: Object,
 
-      _commentsForDiff: Object,
+        _commentsForDiff: Object,
 
-      /**
+        /**
        * Object to contain the path of the next and previous file in the current
        * change and patch range that has comments.
        */
-      _commentSkips: {
-        type: Object,
-        computed: '_computeCommentSkips(_commentMap, _fileList, _path)',
-      },
-      _panelFloatingDisabled: {
-        type: Boolean,
-        value: () => { return window.PANEL_FLOATING_DISABLED; },
-      },
-      _editMode: {
-        type: Boolean,
-        computed: '_computeEditMode(_patchRange.*)',
-      },
-      _isBlameLoaded: Boolean,
-      _isBlameLoading: {
-        type: Boolean,
-        value: false,
-      },
-      _allPatchSets: {
-        type: Array,
-        computed: 'computeAllPatchSets(_change, _change.revisions.*)',
-      },
-      _revisionInfo: {
-        type: Object,
-        computed: '_getRevisionInfo(_change)',
-      },
-      _reviewedFiles: {
-        type: Object,
-        value: () => new Set(),
-      },
-    },
+        _commentSkips: {
+          type: Object,
+          computed: '_computeCommentSkips(_commentMap, _fileList, _path)',
+        },
+        _panelFloatingDisabled: {
+          type: Boolean,
+          value: () => { return window.PANEL_FLOATING_DISABLED; },
+        },
+        _editMode: {
+          type: Boolean,
+          computed: '_computeEditMode(_patchRange.*)',
+        },
+        _isBlameLoaded: Boolean,
+        _isBlameLoading: {
+          type: Boolean,
+          value: false,
+        },
+        _allPatchSets: {
+          type: Array,
+          computed: 'computeAllPatchSets(_change, _change.revisions.*)',
+        },
+        _revisionInfo: {
+          type: Object,
+          computed: '_getRevisionInfo(_change)',
+        },
+        _reviewedFiles: {
+          type: Object,
+          value: () => new Set(),
+        },
+      };
+    }
 
-    behaviors: [
-      Gerrit.FireBehavior,
-      Gerrit.KeyboardShortcutBehavior,
-      Gerrit.PatchSetBehavior,
-      Gerrit.PathListBehavior,
-      Gerrit.RESTClientBehavior,
-    ],
+    static get observers() {
+      return [
+        '_getProjectConfig(_change.project)',
+        '_getFiles(_changeNum, _patchRange.*)',
+        '_setReviewedObserver(_loggedIn, params.*, _prefs)',
+      ];
+    }
 
-    observers: [
-      '_getProjectConfig(_change.project)',
-      '_getFiles(_changeNum, _patchRange.*)',
-      '_setReviewedObserver(_loggedIn, params.*, _prefs)',
-    ],
-
-    keyBindings: {
-      esc: '_handleEscKey',
-    },
+    get keyBindings() {
+      return {
+        esc: '_handleEscKey',
+      };
+    }
 
     keyboardShortcuts() {
       return {
@@ -230,37 +242,38 @@
         [this.Shortcut.EXPAND_ALL_COMMENT_THREADS]: null,
         [this.Shortcut.COLLAPSE_ALL_COMMENT_THREADS]: null,
       };
-    },
+    }
 
     attached() {
+      super.attached();
       this._getLoggedIn().then(loggedIn => {
         this._loggedIn = loggedIn;
       });
 
       this.$.cursor.push('diffs', this.$.diffHost);
-    },
+    }
 
     _getLoggedIn() {
       return this.$.restAPI.getLoggedIn();
-    },
+    }
 
     _getProjectConfig(project) {
       return this.$.restAPI.getProjectConfig(project).then(
           config => {
             this._projectConfig = config;
           });
-    },
+    }
 
     _getChangeDetail(changeNum) {
       return this.$.restAPI.getDiffChangeDetail(changeNum).then(change => {
         this._change = change;
         return change;
       });
-    },
+    }
 
     _getChangeEdit(changeNum) {
       return this.$.restAPI.getChangeEdit(this._changeNum);
-    },
+    }
 
     _getFiles(changeNum, patchRangeRecord) {
       // Polymer 2: check for undefined
@@ -274,25 +287,25 @@
           changeNum, patchRange).then(files => {
         this._fileList = files;
       });
-    },
+    }
 
     _getDiffPreferences() {
       return this.$.restAPI.getDiffPreferences().then(prefs => {
         this._prefs = prefs;
       });
-    },
+    }
 
     _getPreferences() {
       return this.$.restAPI.getPreferences();
-    },
+    }
 
     _getWindowWidth() {
       return window.innerWidth;
-    },
+    }
 
     _handleReviewedChange(e) {
       this._setReviewed(Polymer.dom(e).rootTarget.checked);
-    },
+    }
 
     _setReviewed(reviewed) {
       if (this._editMode) { return; }
@@ -301,12 +314,12 @@
         this.fire('show-alert', {message: ERR_REVIEW_STATUS});
         throw err;
       });
-    },
+    }
 
     _saveReviewedState(reviewed) {
       return this.$.restAPI.saveFileReviewed(this._changeNum,
           this._patchRange.patchNum, this._path, reviewed);
-    },
+    }
 
     _handleToggleFileReviewed(e) {
       if (this.shouldSuppressKeyboardShortcut(e) ||
@@ -314,7 +327,7 @@
 
       e.preventDefault();
       this._setReviewed(!this.$.reviewed.checked);
-    },
+    }
 
     _handleEscKey(e) {
       if (this.shouldSuppressKeyboardShortcut(e) ||
@@ -322,21 +335,21 @@
 
       e.preventDefault();
       this.$.diffHost.displayLine = false;
-    },
+    }
 
     _handleLeftPane(e) {
       if (this.shouldSuppressKeyboardShortcut(e)) { return; }
 
       e.preventDefault();
       this.$.cursor.moveLeft();
-    },
+    }
 
     _handleRightPane(e) {
       if (this.shouldSuppressKeyboardShortcut(e)) { return; }
 
       e.preventDefault();
       this.$.cursor.moveRight();
-    },
+    }
 
     _handlePrevLineOrFileWithComments(e) {
       if (this.shouldSuppressKeyboardShortcut(e)) { return; }
@@ -350,7 +363,7 @@
       e.preventDefault();
       this.$.diffHost.displayLine = true;
       this.$.cursor.moveUp();
-    },
+    }
 
     _handleNextLineOrFileWithComments(e) {
       if (this.shouldSuppressKeyboardShortcut(e)) { return; }
@@ -364,7 +377,7 @@
       e.preventDefault();
       this.$.diffHost.displayLine = true;
       this.$.cursor.moveDown();
-    },
+    }
 
     _moveToPreviousFileWithComment() {
       if (!this._commentSkips) { return; }
@@ -378,7 +391,7 @@
 
       Gerrit.Nav.navigateToDiff(this._change, this._commentSkips.previous,
           this._patchRange.patchNum, this._patchRange.basePatchNum);
-    },
+    }
 
     _moveToNextFileWithComment() {
       if (!this._commentSkips) { return; }
@@ -391,14 +404,14 @@
 
       Gerrit.Nav.navigateToDiff(this._change, this._commentSkips.next,
           this._patchRange.patchNum, this._patchRange.basePatchNum);
-    },
+    }
 
     _handleNewComment(e) {
       if (this.shouldSuppressKeyboardShortcut(e) ||
           this.modifierPressed(e)) { return; }
       e.preventDefault();
       this.$.cursor.createCommentInPlace();
-    },
+    }
 
     _handlePrevFile(e) {
       // Check for meta key to avoid overriding native chrome shortcut.
@@ -407,7 +420,7 @@
 
       e.preventDefault();
       this._navToFile(this._path, this._fileList, -1);
-    },
+    }
 
     _handleNextFile(e) {
       // Check for meta key to avoid overriding native chrome shortcut.
@@ -416,7 +429,7 @@
 
       e.preventDefault();
       this._navToFile(this._path, this._fileList, 1);
-    },
+    }
 
     _handleNextChunkOrCommentThread(e) {
       if (this.shouldSuppressKeyboardShortcut(e)) { return; }
@@ -428,7 +441,7 @@
         if (this.modifierPressed(e)) { return; }
         this.$.cursor.moveToNextChunk();
       }
-    },
+    }
 
     _handlePrevChunkOrCommentThread(e) {
       if (this.shouldSuppressKeyboardShortcut(e)) { return; }
@@ -440,7 +453,7 @@
         if (this.modifierPressed(e)) { return; }
         this.$.cursor.moveToPreviousChunk();
       }
-    },
+    }
 
     _handleOpenReplyDialogOrToggleLeftPane(e) {
       if (this.shouldSuppressKeyboardShortcut(e)) { return; }
@@ -458,7 +471,7 @@
       this.set('changeViewState.showReplyDialog', true);
       e.preventDefault();
       this._navToChangeView();
-    },
+    }
 
     _handleUpToChange(e) {
       if (this.shouldSuppressKeyboardShortcut(e) ||
@@ -466,7 +479,7 @@
 
       e.preventDefault();
       this._navToChangeView();
-    },
+    }
 
     _handleCommaKey(e) {
       if (this.shouldSuppressKeyboardShortcut(e) ||
@@ -475,7 +488,7 @@
 
       e.preventDefault();
       this.$.diffPreferencesDialog.open();
-    },
+    }
 
     _handleToggleDiffMode(e) {
       if (this.shouldSuppressKeyboardShortcut(e) ||
@@ -487,7 +500,7 @@
       } else {
         this.$.modeSelect.setMode(DiffViewMode.SIDE_BY_SIDE);
       }
-    },
+    }
 
     _navToChangeView() {
       if (!this._changeNum || !this._patchRange.patchNum) { return; }
@@ -495,7 +508,7 @@
           this._change,
           this._patchRange,
           this._change && this._change.revisions);
-    },
+    }
 
     _navToFile(path, fileList, direction) {
       const newPath = this._getNavLinkPath(path, fileList, direction);
@@ -511,7 +524,7 @@
 
       Gerrit.Nav.navigateToDiff(this._change, newPath.path,
           this._patchRange.patchNum, this._patchRange.basePatchNum);
-    },
+    }
 
     /**
      * @param {?string} path The path of the current file being shown.
@@ -535,7 +548,7 @@
             this._change && this._change.revisions);
       }
       return this._getDiffUrl(this._change, this._patchRange, newPath.path);
-    },
+    }
 
     /**
      * Gives an object representing the target of navigating either left or
@@ -575,7 +588,7 @@
       }
 
       return {path: fileList[idx]};
-    },
+    }
 
     _getReviewedFiles(changeNum, patchNum) {
       return this.$.restAPI.getReviewedFiles(changeNum, patchNum)
@@ -583,13 +596,13 @@
             this._reviewedFiles = new Set(files);
             return this._reviewedFiles;
           });
-    },
+    }
 
     _getReviewedStatus(editMode, changeNum, patchNum, path) {
       if (editMode) { return Promise.resolve(false); }
       return this._getReviewedFiles(changeNum, patchNum)
           .then(files => files.has(path));
-    },
+    }
 
     _paramsChanged(value) {
       if (value.view !== Gerrit.Nav.View.DIFF) { return; }
@@ -676,7 +689,7 @@
         // If diff view displayed has not ended yet, it ends here.
         this.$.reporting.diffViewDisplayed();
       });
-    },
+    }
 
     _changeViewStateChanged(changeViewState) {
       if (changeViewState.diffMode === null) {
@@ -685,7 +698,7 @@
           this.set('changeViewState.diffMode', prefs.default_diff_view);
         });
       }
-    },
+    }
 
     _setReviewedObserver(_loggedIn, paramsRecord, _prefs) {
       // Polymer 2: check for undefined
@@ -709,7 +722,7 @@
       if (params.view === Gerrit.Nav.View.DIFF) {
         this._setReviewed(true);
       }
-    },
+    }
 
     /**
      * If the params specify a diff address then configure the diff cursor.
@@ -722,14 +735,14 @@
         this.$.cursor.side = DiffSides.RIGHT;
       }
       this.$.cursor.initialLineNumber = params.lineNum;
-    },
+    }
 
     _getLineOfInterest(params) {
       // If there is a line number specified, pass it along to the diff so that
       // it will not get collapsed.
       if (!params.lineNum) { return null; }
       return {number: params.lineNum, leftSide: params.leftSide};
-    },
+    }
 
     _pathChanged(path) {
       if (path) {
@@ -741,7 +754,7 @@
 
       this.set('changeViewState.selectedFileIndex',
           this._fileList.indexOf(path));
-    },
+    }
 
     _getDiffUrl(change, patchRange, path) {
       if ([change, patchRange, path].some(arg => arg === undefined)) {
@@ -749,7 +762,7 @@
       }
       return Gerrit.Nav.getUrlForDiff(change, path, patchRange.patchNum,
           patchRange.basePatchNum);
-    },
+    }
 
     _patchRangeStr(patchRange) {
       let patchStr = patchRange.patchNum;
@@ -758,7 +771,7 @@
         patchStr = patchRange.basePatchNum + '..' + patchRange.patchNum;
       }
       return patchStr;
-    },
+    }
 
     /**
      * When the latest patch of the change is selected (and there is no base
@@ -782,7 +795,7 @@
         basePatchNum = patchRange.basePatchNum;
       }
       return {patchNum, basePatchNum};
-    },
+    }
 
     _getChangePath(change, patchRange, revisions) {
       if ([change, patchRange].some(arg => arg === undefined)) {
@@ -791,16 +804,16 @@
       const range = this._getChangeUrlRange(patchRange, revisions);
       return Gerrit.Nav.getUrlForChange(change, range.patchNum,
           range.basePatchNum);
-    },
+    }
 
     _navigateToChange(change, patchRange, revisions) {
       const range = this._getChangeUrlRange(patchRange, revisions);
       Gerrit.Nav.navigateToChange(change, range.patchNum, range.basePatchNum);
-    },
+    }
 
     _computeChangePath(change, patchRangeRecord, revisions) {
       return this._getChangePath(change, patchRangeRecord.base, revisions);
-    },
+    }
 
     _formatFilesForDropdown(fileList, patchNum, changeComments) {
       // Polymer 2: check for undefined
@@ -824,7 +837,7 @@
         });
       }
       return dropdownContent;
-    },
+    }
 
     _computeCommentString(changeComments, patchNum, path) {
       const unresolvedCount = changeComments.computeUnresolvedNum(patchNum,
@@ -840,11 +853,11 @@
           (commentString && unresolvedString ? ', ' : '') +
           // Add parentheses around unresolved if it exists.
           (unresolvedString ? `${unresolvedString}` : '');
-    },
+    }
 
     _computePrefsButtonHidden(prefs, prefsDisabled) {
       return prefsDisabled || !prefs;
-    },
+    }
 
     _handleFileChange(e) {
       // This is when it gets set initially.
@@ -855,7 +868,7 @@
 
       Gerrit.Nav.navigateToDiff(this._change, path, this._patchRange.patchNum,
           this._patchRange.basePatchNum);
-    },
+    }
 
     _handleFileTap(e) {
       // async is needed so that that the click event is fired before the
@@ -863,7 +876,7 @@
       this.async(() => {
         this.$.dropdown.close();
       }, 1);
-    },
+    }
 
     _handlePatchChange(e) {
       const {basePatchNum, patchNum} = e.detail;
@@ -871,12 +884,12 @@
           this.patchNumEquals(patchNum, this._patchRange.patchNum)) { return; }
       Gerrit.Nav.navigateToDiff(
           this._change, this._path, patchNum, basePatchNum);
-    },
+    }
 
     _handlePrefsTap(e) {
       e.preventDefault();
       this.$.diffPreferencesDialog.open();
-    },
+    }
 
     /**
      * _getDiffViewMode: Get the diff view (side-by-side or unified) based on
@@ -901,11 +914,11 @@
       } else {
         return 'SIDE_BY_SIDE';
       }
-    },
+    }
 
     _computeModeSelectHideClass(isImageDiff) {
       return isImageDiff ? 'hide' : '';
-    },
+    }
 
     _onLineSelected(e, detail) {
       this.$.cursor.moveToLineNumber(detail.number, detail.side);
@@ -917,7 +930,7 @@
           this._change.project, this._path, this._patchRange.patchNum,
           this._patchRange.basePatchNum, number, leftSide);
       history.replaceState(null, '', url);
-    },
+    }
 
     _computeDownloadDropdownLinks(
         project, changeNum, patchRange, path, diff) {
@@ -956,7 +969,7 @@
       }
 
       return links;
-    },
+    }
 
     _computeDownloadFileLink(
         project, changeNum, patchRange, path, isBase) {
@@ -976,13 +989,13 @@
       }
 
       return url;
-    },
+    }
 
     _computeDownloadPatchLink(project, changeNum, patchRange, path) {
       let url = this.changeBaseURL(project, changeNum, patchRange.patchNum);
       url += '/patch?zip&path=' + encodeURIComponent(path);
       return url;
-    },
+    }
 
     _loadComments() {
       return this.$.commentAPI.loadAll(this._changeNum).then(comments => {
@@ -992,20 +1005,20 @@
         this._commentsForDiff = this._getCommentsForPath(this._path,
             this._patchRange, this._projectConfig);
       });
-    },
+    }
 
     _getPaths(patchRange) {
       return this._changeComments.getPaths(patchRange);
-    },
+    }
 
     _getCommentsForPath(path, patchRange, projectConfig) {
       return this._changeComments.getCommentsBySideForPath(path, patchRange,
           projectConfig);
-    },
+    }
 
     _getDiffDrafts() {
       return this.$.restAPI.getDiffDrafts(this._changeNum);
-    },
+    }
 
     _computeCommentSkips(commentMap, fileList, path) {
       // Polymer 2: check for undefined
@@ -1038,13 +1051,13 @@
       }
 
       return skips;
-    },
+    }
 
     _computeDiffClass(panelFloatingDisabled) {
       if (panelFloatingDisabled) {
         return 'noOverflow';
       }
-    },
+    }
 
     /**
      * @param {!Object} patchRangeRecord
@@ -1052,19 +1065,19 @@
     _computeEditMode(patchRangeRecord) {
       const patchRange = patchRangeRecord.base || {};
       return this.patchNumEquals(patchRange.patchNum, this.EDIT_NAME);
-    },
+    }
 
     /**
      * @param {boolean} editMode
      */
     _computeContainerClass(editMode) {
       return editMode ? 'editMode' : '';
-    },
+    }
 
     _computeBlameToggleLabel(loaded, loading) {
       if (loaded) { return 'Hide blame'; }
       return 'Show blame';
-    },
+    }
 
     /**
      * Load and display blame information if it has not already been loaded.
@@ -1086,15 +1099,15 @@
           .catch(() => {
             this._isBlameLoading = false;
           });
-    },
+    }
 
     _computeBlameLoaderClass(isImageDiff) {
       return !isImageDiff ? 'show' : '';
-    },
+    }
 
     _getRevisionInfo(change) {
       return new Gerrit.RevisionInfo(change);
-    },
+    }
 
     _computeFileNum(file, files) {
       // Polymer 2: check for undefined
@@ -1103,7 +1116,7 @@
       }
 
       return files.findIndex(({value}) => value === file) + 1;
-    },
+    }
 
     /**
      * @param {number} fileNum
@@ -1115,16 +1128,16 @@
         return 'show';
       }
       return '';
-    },
+    }
 
     _handleExpandAllDiffContext(e) {
       if (this.shouldSuppressKeyboardShortcut(e)) { return; }
       this.$.diffHost.expandAllContext();
-    },
+    }
 
     _computeDiffPrefsDisabled(disableDiffPrefs, loggedIn) {
       return disableDiffPrefs || !loggedIn;
-    },
+    }
 
     _handleNextUnreviewedFile(e) {
       if (this.shouldSuppressKeyboardShortcut(e)) { return; }
@@ -1135,10 +1148,12 @@
           .filter(file =>
             (file === this._path || !this._reviewedFiles.has(file)));
       this._navToFile(this._path, unreviewedFiles, 1);
-    },
+    }
 
     _handleReloadingDiffPreference() {
       this._getDiffPreferences();
-    },
-  });
+    }
+  }
+
+  customElements.define(GrDiffView.is, GrDiffView);
 })();
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view_test.html b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view_test.html
index 573d75f..3a5c0bc 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view_test.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-diff-view</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>
diff --git a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff-group_test.html b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff-group_test.html
index 16e8036..686410c 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff-group_test.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff-group_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-diff-group</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/web-component-tester/browser.js"></script>
diff --git a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.js b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.js
index e7c7189..727115b 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.js
@@ -91,9 +91,17 @@
 
   const RENDER_DIFF_TABLE_DEBOUNCE_NAME = 'renderDiffTable';
 
-  Polymer({
-    is: 'gr-diff',
-
+  /**
+    * @appliesMixin Gerrit.FireMixin
+    * @appliesMixin Gerrit.PatchSetMixin
+    */
+  class GrDiff extends Polymer.mixinBehaviors( [
+    Gerrit.FireBehavior,
+    Gerrit.PatchSetBehavior,
+  ], Polymer.GestureEventListeners(
+      Polymer.LegacyElementMixin(
+          Polymer.Element))) {
+    static get is() { return 'gr-diff'; }
     /**
      * Fired when the user selects a line.
      * @event line-selected
@@ -126,89 +134,90 @@
      * @event diff-context-expanded
      */
 
-    properties: {
-      changeNum: String,
-      noAutoRender: {
-        type: Boolean,
-        value: false,
-      },
-      /** @type {?} */
-      patchRange: Object,
-      path: {
-        type: String,
-        observer: '_pathObserver',
-      },
-      prefs: {
-        type: Object,
-        observer: '_prefsObserver',
-      },
-      projectName: String,
-      displayLine: {
-        type: Boolean,
-        value: false,
-      },
-      isImageDiff: {
-        type: Boolean,
-      },
-      commitRange: Object,
-      hidden: {
-        type: Boolean,
-        reflectToAttribute: true,
-      },
-      noRenderOnPrefsChange: Boolean,
-      /** @type {!Array<!Gerrit.HoveredRange>} */
-      _commentRanges: {
-        type: Array,
-        value: () => [],
-      },
-      /** @type {!Array<!Gerrit.CoverageRange>} */
-      coverageRanges: {
-        type: Array,
-        value: () => [],
-      },
-      lineWrapping: {
-        type: Boolean,
-        value: false,
-        observer: '_lineWrappingObserver',
-      },
-      viewMode: {
-        type: String,
-        value: DiffViewMode.SIDE_BY_SIDE,
-        observer: '_viewModeObserver',
-      },
+    static get properties() {
+      return {
+        changeNum: String,
+        noAutoRender: {
+          type: Boolean,
+          value: false,
+        },
+        /** @type {?} */
+        patchRange: Object,
+        path: {
+          type: String,
+          observer: '_pathObserver',
+        },
+        prefs: {
+          type: Object,
+          observer: '_prefsObserver',
+        },
+        projectName: String,
+        displayLine: {
+          type: Boolean,
+          value: false,
+        },
+        isImageDiff: {
+          type: Boolean,
+        },
+        commitRange: Object,
+        hidden: {
+          type: Boolean,
+          reflectToAttribute: true,
+        },
+        noRenderOnPrefsChange: Boolean,
+        /** @type {!Array<!Gerrit.HoveredRange>} */
+        _commentRanges: {
+          type: Array,
+          value: () => [],
+        },
+        /** @type {!Array<!Gerrit.CoverageRange>} */
+        coverageRanges: {
+          type: Array,
+          value: () => [],
+        },
+        lineWrapping: {
+          type: Boolean,
+          value: false,
+          observer: '_lineWrappingObserver',
+        },
+        viewMode: {
+          type: String,
+          value: DiffViewMode.SIDE_BY_SIDE,
+          observer: '_viewModeObserver',
+        },
 
-      /** @type ?Gerrit.LineOfInterest */
-      lineOfInterest: Object,
+        /** @type ?Gerrit.LineOfInterest */
+        lineOfInterest: Object,
 
-      loading: {
-        type: Boolean,
-        value: false,
-        observer: '_loadingChanged',
-      },
+        loading: {
+          type: Boolean,
+          value: false,
+          observer: '_loadingChanged',
+        },
 
-      loggedIn: {
-        type: Boolean,
-        value: false,
-      },
-      diff: {
-        type: Object,
-        observer: '_diffChanged',
-      },
-      _diffHeaderItems: {
-        type: Array,
-        value: [],
-        computed: '_computeDiffHeaderItems(diff.*)',
-      },
-      _diffTableClass: {
-        type: String,
-        value: '',
-      },
-      /** @type {?Object} */
-      baseImage: Object,
-      /** @type {?Object} */
-      revisionImage: Object,
+        loggedIn: {
+          type: Boolean,
+          value: false,
+        },
+        diff: {
+          type: Object,
+          observer: '_diffChanged',
+        },
+        _diffHeaderItems: {
+          type: Array,
+          value: [],
+          computed: '_computeDiffHeaderItems(diff.*)',
+        },
+        _diffTableClass: {
+          type: String,
+          value: '',
+        },
+        /** @type {?Object} */
+        baseImage: Object,
+        /** @type {?Object} */
+        revisionImage: Object,
 
-      /**
+        /**
        * Whether the safety check for large diffs when whole-file is set has
        * been bypassed. If the value is null, then the safety has not been
        * bypassed. If the value is a number, then that number represents the
@@ -216,83 +225,86 @@
        *
        * @type (number|null)
        */
-      _safetyBypass: {
-        type: Number,
-        value: null,
-      },
+        _safetyBypass: {
+          type: Number,
+          value: null,
+        },
 
-      _showWarning: Boolean,
+        _showWarning: Boolean,
 
-      /** @type {?string} */
-      errorMessage: {
-        type: String,
-        value: null,
-      },
+        /** @type {?string} */
+        errorMessage: {
+          type: String,
+          value: null,
+        },
 
-      /** @type {?Object} */
-      blame: {
-        type: Object,
-        value: null,
-        observer: '_blameChanged',
-      },
+        /** @type {?Object} */
+        blame: {
+          type: Object,
+          value: null,
+          observer: '_blameChanged',
+        },
 
-      parentIndex: Number,
+        parentIndex: Number,
 
-      _newlineWarning: {
-        type: String,
-        computed: '_computeNewlineWarning(diff)',
-      },
+        _newlineWarning: {
+          type: String,
+          computed: '_computeNewlineWarning(diff)',
+        },
 
-      _diffLength: Number,
+        _diffLength: Number,
 
-      /**
+        /**
        * Observes comment nodes added or removed after the initial render.
        * Can be used to unregister when the entire diff is (re-)rendered or upon
        * detachment.
        * @type {?PolymerDomApi.ObserveHandle}
        */
-      _incrementalNodeObserver: Object,
+        _incrementalNodeObserver: Object,
 
-      /**
+        /**
        * Observes comment nodes added or removed at any point.
        * Can be used to unregister upon detachment.
        * @type {?PolymerDomApi.ObserveHandle}
        */
-      _nodeObserver: Object,
+        _nodeObserver: Object,
 
-      /** Set by Polymer. */
-      isAttached: Boolean,
-      layers: Array,
-    },
+        /** Set by Polymer. */
+        isAttached: Boolean,
+        layers: Array,
+      };
+    }
 
-    behaviors: [
-      Gerrit.FireBehavior,
-      Gerrit.PatchSetBehavior,
-    ],
+    static get observers() {
+      return [
+        '_enableSelectionObserver(loggedIn, isAttached)',
+      ];
+    }
 
-    listeners: {
-      'create-range-comment': '_handleCreateRangeComment',
-      'render-content': '_handleRenderContent',
-    },
-
-    observers: [
-      '_enableSelectionObserver(loggedIn, isAttached)',
-    ],
+    created() {
+      super.created();
+      this.addEventListener('create-range-comment',
+          e => this._handleCreateRangeComment(e));
+      this.addEventListener('render-content',
+          () => this._handleRenderContent());
+    }
 
     attached() {
+      super.attached();
       this._observeNodes();
-    },
+    }
 
     detached() {
+      super.detached();
       this._unobserveIncrementalNodes();
       this._unobserveNodes();
-    },
+    }
 
     showNoChangeMessage(loading, prefs, diffLength) {
       return !loading &&
         prefs && prefs.ignore_whitespace !== 'IGNORE_NONE'
         && diffLength === 0;
-    },
+    }
 
     _enableSelectionObserver(loggedIn, isAttached) {
       // Polymer 2: check for undefined
@@ -307,7 +319,7 @@
         this.unlisten(document, 'selectionchange', '_handleSelectionChange');
         this.unlisten(document, 'mouseup', '_handleMouseUp');
       }
-    },
+    }
 
     _handleSelectionChange() {
       // Because of shadow DOM selections, we handle the selectionchange here,
@@ -315,7 +327,7 @@
       // corresponding range is determined and normalized.
       const selection = this._getShadowOrDocumentSelection();
       this.$.highlights.handleSelectionChange(selection, false);
-    },
+    }
 
     _handleMouseUp(e) {
       // To handle double-click outside of text creating comments, we check on
@@ -323,7 +335,7 @@
       // can't do that on selection change since the user may still be dragging.
       const selection = this._getShadowOrDocumentSelection();
       this.$.highlights.handleSelectionChange(selection, true);
-    },
+    }
 
     /** Gets the current selection, preferring the shadow DOM selection. */
     _getShadowOrDocumentSelection() {
@@ -334,7 +346,7 @@
       return this.root.getSelection ?
         this.root.getSelection() :
         document.getSelection();
-    },
+    }
 
     _observeNodes() {
       this._nodeObserver = Polymer.dom(this).observeNodes(info => {
@@ -343,7 +355,7 @@
         this._updateRanges(addedThreadEls, removedThreadEls);
         this._redispatchHoverEvents(addedThreadEls);
       });
-    },
+    }
 
     _updateRanges(addedThreadEls, removedThreadEls) {
       function commentRangeFromThreadEl(threadEl) {
@@ -369,7 +381,7 @@
       if (addedCommentRanges && addedCommentRanges.length) {
         this.push('_commentRanges', ...addedCommentRanges);
       }
-    },
+    }
 
     /**
      * The key locations based on the comments and line of interests,
@@ -400,7 +412,7 @@
         }
       }
       return keyLocations;
-    },
+    }
 
     // Dispatch events that are handled by the gr-diff-highlight.
     _redispatchHoverEvents(addedThreadEls) {
@@ -414,13 +426,13 @@
               'comment-thread-mouseleave', {bubbles: true, composed: true}));
         });
       }
-    },
+    }
 
     /** Cancel any remaining diff builder rendering work. */
     cancel() {
       this.$.diffBuilder.cancel();
       this.cancelDebouncer(RENDER_DIFF_TABLE_DEBOUNCE_NAME);
-    },
+    }
 
     /** @return {!Array<!HTMLElement>} */
     getCursorStops() {
@@ -430,16 +442,16 @@
 
       return Array.from(
           Polymer.dom(this.root).querySelectorAll('.diff-row'));
-    },
+    }
 
     /** @return {boolean} */
     isRangeSelected() {
       return !!this.$.highlights.selectedRange;
-    },
+    }
 
     toggleLeftDiff() {
       this.toggleClass('no-left');
-    },
+    }
 
     _blameChanged(newValue) {
       this.$.diffBuilder.setBlame(newValue);
@@ -448,7 +460,7 @@
       } else {
         this.classList.remove('showBlame');
       }
-    },
+    }
 
     /** @return {string} */
     _computeContainerClass(loggedIn, viewMode, displayLine) {
@@ -473,7 +485,7 @@
         classes.push('displayLine');
       }
       return classes.join(' ');
-    },
+    }
 
     _handleTap(e) {
       const el = Polymer.dom(e).localTarget;
@@ -491,7 +503,7 @@
         const target = this.$.diffBuilder.getLineElByChild(el);
         if (target) { this._selectLine(target); }
       }
-    },
+    }
 
     _selectLine(el) {
       this.fire('line-selected', {
@@ -499,7 +511,7 @@
         number: el.getAttribute('data-value'),
         path: this.path,
       });
-    },
+    }
 
     addDraftAtLine(el) {
       this._selectLine(el);
@@ -515,7 +527,7 @@
         }
       }
       this._createComment(el, lineNum);
-    },
+    }
 
     createRangeComment() {
       if (!this.isRangeSelected()) {
@@ -523,7 +535,7 @@
       }
       const {side, range} = this.$.highlights.selectedRange;
       this._createCommentForSelection(side, range);
-    },
+    }
 
     _createCommentForSelection(side, range) {
       const lineNum = range.end_line;
@@ -531,13 +543,13 @@
       if (this._isValidElForComment(lineEl)) {
         this._createComment(lineEl, lineNum, side, range);
       }
-    },
+    }
 
     _handleCreateRangeComment(e) {
       const range = e.detail.range;
       const side = e.detail.side;
       this._createCommentForSelection(side, range);
-    },
+    }
 
     /** @return {boolean} */
     _isValidElForComment(el) {
@@ -561,7 +573,7 @@
         return false;
       }
       return true;
-    },
+    }
 
     /**
      * @param {!Object} lineEl
@@ -589,11 +601,11 @@
           range,
         },
       }));
-    },
+    }
 
     _getThreadGroupForLine(contentEl) {
       return contentEl.querySelector('.thread-group');
-    },
+    }
 
     /**
      * Gets or creates a comment thread group for a specific line and side on a
@@ -612,7 +624,7 @@
         contentEl.appendChild(threadGroupEl);
       }
       return threadGroupEl;
-    },
+    }
 
     /**
      * The value to be used for the patch number of new comments created at the
@@ -638,7 +650,7 @@
         patchNum = this.patchRange.basePatchNum;
       }
       return patchNum;
-    },
+    }
 
     /** @return {boolean} */
     _getIsParentCommentByLineAndContent(lineEl, contentEl) {
@@ -649,7 +661,7 @@
         return true;
       }
       return false;
-    },
+    }
 
     /** @return {string} */
     _getCommentSideByLineAndContent(lineEl, contentEl) {
@@ -659,7 +671,7 @@
         side = 'left';
       }
       return side;
-    },
+    }
 
     _prefsObserver(newPrefs, oldPrefs) {
       // Scan the preference objects one level deep to see if they differ.
@@ -675,16 +687,16 @@
       if (differ) {
         this._prefsChanged(newPrefs);
       }
-    },
+    }
 
     _pathObserver() {
       // Call _prefsChanged(), because line-limit style value depends on path.
       this._prefsChanged(this.prefs);
-    },
+    }
 
     _viewModeObserver() {
       this._prefsChanged(this.prefs);
-    },
+    }
 
     /** @param {boolean} newValue */
     _loadingChanged(newValue) {
@@ -695,11 +707,11 @@
         this._showWarning = false;
         this.clearDiffContent();
       }
-    },
+    }
 
     _lineWrappingObserver() {
       this._prefsChanged(this.prefs);
-    },
+    }
 
     _prefsChanged(prefs) {
       if (!prefs) { return; }
@@ -730,14 +742,14 @@
       if (this.diff && !this.noRenderOnPrefsChange) {
         this._debounceRenderDiffTable();
       }
-    },
+    }
 
     _diffChanged(newValue) {
       if (newValue) {
         this._diffLength = this.getDiffLength(newValue);
         this._debounceRenderDiffTable();
       }
-    },
+    }
 
     /**
      * When called multiple times from the same microtask, will call
@@ -752,7 +764,7 @@
     _debounceRenderDiffTable() {
       this.debounce(
           RENDER_DIFF_TABLE_DEBOUNCE_NAME, () => this._renderDiffTable());
-    },
+    }
 
     _renderDiffTable() {
       this._unobserveIncrementalNodes();
@@ -782,7 +794,7 @@
                   detail: {contentRendered: true},
                 }));
           });
-    },
+    }
 
     _handleRenderContent() {
       this._incrementalNodeObserver = Polymer.dom(this).observeNodes(info => {
@@ -821,19 +833,19 @@
           lastEl.replaceWith(lastEl);
         }
       });
-    },
+    }
 
     _unobserveIncrementalNodes() {
       if (this._incrementalNodeObserver) {
         Polymer.dom(this).unobserveNodes(this._incrementalNodeObserver);
       }
-    },
+    }
 
     _unobserveNodes() {
       if (this._nodeObserver) {
         Polymer.dom(this).unobserveNodes(this._nodeObserver);
       }
-    },
+    }
 
     /**
      * Get the preferences object including the safety bypass context (if any).
@@ -843,12 +855,12 @@
         return Object.assign({}, this.prefs, {context: this._safetyBypass});
       }
       return this.prefs;
-    },
+    }
 
     clearDiffContent() {
       this._unobserveIncrementalNodes();
       this.$.diffTable.innerHTML = null;
-    },
+    }
 
     /** @return {!Array} */
     _computeDiffHeaderItems(diffInfoRecord) {
@@ -861,27 +873,27 @@
             item.startsWith('--- ') ||
             item === 'Binary files differ');
       });
-    },
+    }
 
     /** @return {boolean} */
     _computeDiffHeaderHidden(items) {
       return items.length === 0;
-    },
+    }
 
     _handleFullBypass() {
       this._safetyBypass = FULL_CONTEXT;
       this._debounceRenderDiffTable();
-    },
+    }
 
     _handleLimitedBypass() {
       this._safetyBypass = LIMITED_CONTEXT;
       this._debounceRenderDiffTable();
-    },
+    }
 
     /** @return {string} */
     _computeWarningClass(showWarning) {
       return showWarning ? 'warn' : '';
-    },
+    }
 
     /**
      * @param {string} errorMessage
@@ -889,11 +901,11 @@
      */
     _computeErrorClass(errorMessage) {
       return errorMessage ? 'showError' : '';
-    },
+    }
 
     expandAllContext() {
       this._handleFullBypass();
-    },
+    }
 
     /**
      * Find the last chunk for the given side.
@@ -929,7 +941,7 @@
       if (chunkIndex === -1) { return null; }
 
       return chunk;
-    },
+    }
 
     /**
      * Check whether the specified side of the diff has a trailing newline.
@@ -950,7 +962,7 @@
         lines = leftSide ? chunk.a : chunk.b;
       }
       return lines[lines.length - 1] === '';
-    },
+    }
 
     /**
      * @param {!Object} diff
@@ -968,7 +980,7 @@
       }
       if (!messages.length) { return null; }
       return messages.join(' — ');
-    },
+    }
 
     /**
      * @param {string} warning
@@ -978,7 +990,7 @@
     _computeNewlineWarningClass(warning, loading) {
       if (loading || !warning) { return 'newlineWarning hidden'; }
       return 'newlineWarning';
-    },
+    }
 
     /**
      * Get the approximate length of the diff as the sum of the maximum
@@ -997,6 +1009,8 @@
               sec.hasOwnProperty('b') ? sec.b.length : 0);
         }
       }, 0);
-    },
-  });
+    }
+  }
+
+  customElements.define(GrDiff.is, GrDiff);
 })();
diff --git a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff_test.html b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff_test.html
index 52a29f9..2805eb4 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff_test.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-diff</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>
diff --git a/polygerrit-ui/app/elements/diff/gr-patch-range-select/gr-patch-range-select.js b/polygerrit-ui/app/elements/diff/gr-patch-range-select/gr-patch-range-select.js
index 52a107d..14148a9 100644
--- a/polygerrit-ui/app/elements/diff/gr-patch-range-select/gr-patch-range-select.js
+++ b/polygerrit-ui/app/elements/diff/gr-patch-range-select/gr-patch-range-select.js
@@ -21,6 +21,9 @@
   const PATCH_DESC_MAX_LENGTH = 500;
 
   /**
+    * @appliesMixin Gerrit.PatchSetMixin
+    */
+  /**
    * Fired when the patch range changes
    *
    * @event patch-range-change
@@ -28,42 +31,47 @@
    * @property {string} patchNum
    * @property {string} basePatchNum
    */
+  class GrPatchRangeSelect extends Polymer.mixinBehaviors( [
+    Gerrit.PatchSetBehavior,
+  ], Polymer.GestureEventListeners(
+      Polymer.LegacyElementMixin(
+          Polymer.Element))) {
+    static get is() { return 'gr-patch-range-select'; }
 
-  Polymer({
-    is: 'gr-patch-range-select',
-
-    properties: {
-      availablePatches: Array,
-      _baseDropdownContent: {
-        type: Object,
-        computed: '_computeBaseDropdownContent(availablePatches, patchNum,' +
+    static get properties() {
+      return {
+        availablePatches: Array,
+        _baseDropdownContent: {
+          type: Object,
+          computed: '_computeBaseDropdownContent(availablePatches, patchNum,' +
             '_sortedRevisions, changeComments, revisionInfo)',
-      },
-      _patchDropdownContent: {
-        type: Object,
-        computed: '_computePatchDropdownContent(availablePatches,' +
+        },
+        _patchDropdownContent: {
+          type: Object,
+          computed: '_computePatchDropdownContent(availablePatches,' +
             'basePatchNum, _sortedRevisions, changeComments)',
-      },
-      changeNum: String,
-      changeComments: Object,
-      /** @type {{ meta_a: !Array, meta_b: !Array}} */
-      filesWeblinks: Object,
-      patchNum: String,
-      basePatchNum: String,
-      revisions: Object,
-      revisionInfo: Object,
-      _sortedRevisions: Array,
-    },
+        },
+        changeNum: String,
+        changeComments: Object,
+        /** @type {{ meta_a: !Array, meta_b: !Array}} */
+        filesWeblinks: Object,
+        patchNum: String,
+        basePatchNum: String,
+        revisions: Object,
+        revisionInfo: Object,
+        _sortedRevisions: Array,
+      };
+    }
 
-    observers: [
-      '_updateSortedRevisions(revisions.*)',
-    ],
-
-    behaviors: [Gerrit.PatchSetBehavior],
+    static get observers() {
+      return [
+        '_updateSortedRevisions(revisions.*)',
+      ];
+    }
 
     _getShaForPatch(patch) {
       return patch.sha.substring(0, 10);
-    },
+    }
 
     _computeBaseDropdownContent(availablePatches, patchNum, _sortedRevisions,
         changeComments, revisionInfo) {
@@ -111,13 +119,13 @@
       }
 
       return dropdownContent;
-    },
+    }
 
     _computeMobileText(patchNum, changeComments, revisions) {
       return `${patchNum}` +
           `${this._computePatchSetCommentsString(changeComments, patchNum)}` +
           `${this._computePatchSetDescription(revisions, patchNum, true)}`;
-    },
+    }
 
     _computePatchDropdownContent(availablePatches, basePatchNum,
         _sortedRevisions, changeComments) {
@@ -143,13 +151,13 @@
         }));
       }
       return dropdownContent;
-    },
+    }
 
     _computeText(patchNum, prefix, changeComments, sha) {
       return `${prefix}${patchNum}` +
         `${this._computePatchSetCommentsString(changeComments, patchNum)}`
           + (` | ${sha}`);
-    },
+    }
 
     _createDropdownEntry(patchNum, prefix, sortedRevisions, changeComments,
         sha) {
@@ -167,12 +175,12 @@
         entry['date'] = date;
       }
       return entry;
-    },
+    }
 
     _updateSortedRevisions(revisionsRecord) {
       const revisions = revisionsRecord.base;
       this._sortedRevisions = this.sortRevisions(Object.values(revisions));
-    },
+    }
 
     /**
      * The basePatchNum should always be <= patchNum -- because sortedRevisions
@@ -185,7 +193,7 @@
     _computeLeftDisabled(basePatchNum, patchNum, sortedRevisions) {
       return this.findSortedIndex(basePatchNum, sortedRevisions) <=
           this.findSortedIndex(patchNum, sortedRevisions);
-    },
+    }
 
     /**
      * The basePatchNum should always be <= patchNum -- because sortedRevisions
@@ -214,7 +222,7 @@
 
       return this.findSortedIndex(basePatchNum, sortedRevisions) <=
           this.findSortedIndex(patchNum, sortedRevisions);
-    },
+    }
 
     _computePatchSetCommentsString(changeComments, patchNum) {
       if (!changeComments) { return; }
@@ -235,7 +243,7 @@
           // Add a comma + space if both comments and unresolved
           (commentString && unresolvedString ? ', ' : '') +
           `${unresolvedString})`;
-    },
+    }
 
     /**
      * @param {!Array} revisions
@@ -247,7 +255,7 @@
       return (rev && rev.description) ?
         (opt_addFrontSpace ? ' ' : '') +
           rev.description.substring(0, PATCH_DESC_MAX_LENGTH) : '';
-    },
+    }
 
     /**
      * @param {!Array} revisions
@@ -256,7 +264,7 @@
     _computePatchSetDate(revisions, patchNum) {
       const rev = this.getRevisionByPatchNum(revisions, patchNum);
       return rev ? rev.created : undefined;
-    },
+    }
 
     /**
      * Catches value-change events from the patchset dropdowns and determines
@@ -274,6 +282,8 @@
 
       this.dispatchEvent(
           new CustomEvent('patch-range-change', {detail, bubbles: false}));
-    },
-  });
+    }
+  }
+
+  customElements.define(GrPatchRangeSelect.is, GrPatchRangeSelect);
 })();
diff --git a/polygerrit-ui/app/elements/diff/gr-patch-range-select/gr-patch-range-select_test.html b/polygerrit-ui/app/elements/diff/gr-patch-range-select/gr-patch-range-select_test.html
index ee893ee..81897cd 100644
--- a/polygerrit-ui/app/elements/diff/gr-patch-range-select/gr-patch-range-select_test.html
+++ b/polygerrit-ui/app/elements/diff/gr-patch-range-select/gr-patch-range-select_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-patch-range-select</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>
diff --git a/polygerrit-ui/app/elements/diff/gr-ranged-comment-layer/gr-ranged-comment-layer.js b/polygerrit-ui/app/elements/diff/gr-ranged-comment-layer/gr-ranged-comment-layer.js
index 2e74ff4..7ee6492 100644
--- a/polygerrit-ui/app/elements/diff/gr-ranged-comment-layer/gr-ranged-comment-layer.js
+++ b/polygerrit-ui/app/elements/diff/gr-ranged-comment-layer/gr-ranged-comment-layer.js
@@ -23,9 +23,10 @@
   const RANGE_HIGHLIGHT = 'style-scope gr-diff range';
   const HOVER_HIGHLIGHT = 'style-scope gr-diff rangeHighlight';
 
-  Polymer({
-    is: 'gr-ranged-comment-layer',
-
+  class GrRangedCommentLayer extends Polymer.GestureEventListeners(
+      Polymer.LegacyElementMixin(
+          Polymer.Element)) {
+    static get is() { return 'gr-ranged-comment-layer'; }
     /**
      * Fired when the range in a range comment was malformed and had to be
      * normalized.
@@ -35,26 +36,30 @@
      * @event normalize-range
      */
 
-    properties: {
+    static get properties() {
+      return {
       /** @type {!Array<!Gerrit.HoveredRange>} */
-      commentRanges: Array,
-      _listeners: {
-        type: Array,
-        value() { return []; },
-      },
-      _rangesMap: {
-        type: Object,
-        value() { return {left: {}, right: {}}; },
-      },
-    },
+        commentRanges: Array,
+        _listeners: {
+          type: Array,
+          value() { return []; },
+        },
+        _rangesMap: {
+          type: Object,
+          value() { return {left: {}, right: {}}; },
+        },
+      };
+    }
 
-    observers: [
-      '_handleCommentRangesChange(commentRanges.*)',
-    ],
+    static get observers() {
+      return [
+        '_handleCommentRangesChange(commentRanges.*)',
+      ];
+    }
 
     get styleModuleName() {
       return 'gr-ranged-comment-styles';
-    },
+    }
 
     /**
      * Layer method to add annotations to a line.
@@ -81,7 +86,7 @@
             range.end - range.start,
             range.hovering ? HOVER_HIGHLIGHT : RANGE_HIGHLIGHT);
       }
-    },
+    }
 
     /**
      * Register a listener for layer updates.
@@ -91,7 +96,7 @@
      */
     addListener(fn) {
       this._listeners.push(fn);
-    },
+    }
 
     /**
      * Notify Layer listeners of changes to annotations.
@@ -103,7 +108,7 @@
       for (const listener of this._listeners) {
         listener(start, end, side);
       }
-    },
+    }
 
     /**
      * Handle change in the ranges by updating the ranges maps and by
@@ -161,7 +166,7 @@
           }
         }
       }
-    },
+    }
 
     _updateRangesMap(side, range, hovering, operation) {
       const forSide = this._rangesMap[side] || (this._rangesMap[side] = {});
@@ -172,7 +177,7 @@
         operation(forLine, start, end, hovering);
       }
       this._notifyUpdateRange(range.start_line, range.end_line, side);
-    },
+    }
 
     _getRangesForLine(line, side) {
       const lineNum = side === 'left' ? line.beforeNumber : line.afterNumber;
@@ -200,6 +205,8 @@
           })
           // Sort the ranges so that hovering highlights are on top.
           .sort((a, b) => a.hovering && !b.hovering ? 1 : 0);
-    },
-  });
+    }
+  }
+
+  customElements.define(GrRangedCommentLayer.is, GrRangedCommentLayer);
 })();
diff --git a/polygerrit-ui/app/elements/diff/gr-ranged-comment-layer/gr-ranged-comment-layer_test.html b/polygerrit-ui/app/elements/diff/gr-ranged-comment-layer/gr-ranged-comment-layer_test.html
index 9d207a5..4ed744b 100644
--- a/polygerrit-ui/app/elements/diff/gr-ranged-comment-layer/gr-ranged-comment-layer_test.html
+++ b/polygerrit-ui/app/elements/diff/gr-ranged-comment-layer/gr-ranged-comment-layer_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-ranged-comment-layer</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>
diff --git a/polygerrit-ui/app/elements/diff/gr-selection-action-box/gr-selection-action-box.js b/polygerrit-ui/app/elements/diff/gr-selection-action-box/gr-selection-action-box.js
index e1b969f..3ef9105 100644
--- a/polygerrit-ui/app/elements/diff/gr-selection-action-box/gr-selection-action-box.js
+++ b/polygerrit-ui/app/elements/diff/gr-selection-action-box/gr-selection-action-box.js
@@ -17,30 +17,38 @@
 (function() {
   'use strict';
 
-  Polymer({
-    is: 'gr-selection-action-box',
-
+  /**
+    * @appliesMixin Gerrit.FireMixin
+    */
+  class GrSelectionActionBox extends Polymer.mixinBehaviors( [
+    Gerrit.FireBehavior,
+  ], Polymer.GestureEventListeners(
+      Polymer.LegacyElementMixin(
+          Polymer.Element))) {
+    static get is() { return 'gr-selection-action-box'; }
     /**
      * Fired when the comment creation action was taken (click).
      *
      * @event create-comment-requested
      */
 
-    properties: {
-      keyEventTarget: {
-        type: Object,
-        value() { return document.body; },
-      },
-      positionBelow: Boolean,
-    },
+    static get properties() {
+      return {
+        keyEventTarget: {
+          type: Object,
+          value() { return document.body; },
+        },
+        positionBelow: Boolean,
+      };
+    }
 
-    behaviors: [
-      Gerrit.FireBehavior,
-    ],
+    created() {
+      super.created();
 
-    listeners: {
-      mousedown: '_handleMouseDown', // See https://crbug.com/gerrit/4767
-    },
+      // See https://crbug.com/gerrit/4767
+      this.addEventListener('mousedown',
+          e => this._handleMouseDown(e));
+    }
 
     placeAbove(el) {
       Polymer.dom.flush();
@@ -51,7 +59,7 @@
           rect.top - parentRect.top - boxRect.height - 6 + 'px';
       this.style.left =
           rect.left - parentRect.left + (rect.width - boxRect.width) / 2 + 'px';
-    },
+    }
 
     placeBelow(el) {
       Polymer.dom.flush();
@@ -62,14 +70,14 @@
       rect.top - parentRect.top + boxRect.height - 6 + 'px';
       this.style.left =
       rect.left - parentRect.left + (rect.width - boxRect.width) / 2 + 'px';
-    },
+    }
 
     _getParentBoundingClientRect() {
       // With native shadow DOM, the parent is the shadow root, not the gr-diff
       // element
       const parent = this.parentElement || this.parentNode.host;
       return parent.getBoundingClientRect();
-    },
+    }
 
     _getTargetBoundingRect(el) {
       let rect;
@@ -82,13 +90,15 @@
         rect = el.getBoundingClientRect();
       }
       return rect;
-    },
+    }
 
     _handleMouseDown(e) {
       if (e.button !== 0) { return; } // 0 = main button
       e.preventDefault();
       e.stopPropagation();
       this.fire('create-comment-requested');
-    },
-  });
+    }
+  }
+
+  customElements.define(GrSelectionActionBox.is, GrSelectionActionBox);
 })();
diff --git a/polygerrit-ui/app/elements/diff/gr-selection-action-box/gr-selection-action-box_test.html b/polygerrit-ui/app/elements/diff/gr-selection-action-box/gr-selection-action-box_test.html
index 9add096..57adf32 100644
--- a/polygerrit-ui/app/elements/diff/gr-selection-action-box/gr-selection-action-box_test.html
+++ b/polygerrit-ui/app/elements/diff/gr-selection-action-box/gr-selection-action-box_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-selection-action-box</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>
diff --git a/polygerrit-ui/app/elements/diff/gr-syntax-layer/gr-syntax-layer.js b/polygerrit-ui/app/elements/diff/gr-syntax-layer/gr-syntax-layer.js
index 6c7d582..b0947cd 100644
--- a/polygerrit-ui/app/elements/diff/gr-syntax-layer/gr-syntax-layer.js
+++ b/polygerrit-ui/app/elements/diff/gr-syntax-layer/gr-syntax-layer.js
@@ -130,54 +130,58 @@
   const GO_BACKSLASH_LITERAL = '\'\\\\\'';
   const GLOBAL_LT_PATTERN = /</g;
 
-  Polymer({
-    is: 'gr-syntax-layer',
+  class GrSyntaxLayer extends Polymer.GestureEventListeners(
+      Polymer.LegacyElementMixin(
+          Polymer.Element)) {
+    static get is() { return 'gr-syntax-layer'; }
 
-    properties: {
-      diff: {
-        type: Object,
-        observer: '_diffChanged',
-      },
-      enabled: {
-        type: Boolean,
-        value: true,
-      },
-      _baseRanges: {
-        type: Array,
-        value() { return []; },
-      },
-      _revisionRanges: {
-        type: Array,
-        value() { return []; },
-      },
-      _baseLanguage: String,
-      _revisionLanguage: String,
-      _listeners: {
-        type: Array,
-        value() { return []; },
-      },
-      /** @type {?number} */
-      _processHandle: Number,
-      /**
+    static get properties() {
+      return {
+        diff: {
+          type: Object,
+          observer: '_diffChanged',
+        },
+        enabled: {
+          type: Boolean,
+          value: true,
+        },
+        _baseRanges: {
+          type: Array,
+          value() { return []; },
+        },
+        _revisionRanges: {
+          type: Array,
+          value() { return []; },
+        },
+        _baseLanguage: String,
+        _revisionLanguage: String,
+        _listeners: {
+          type: Array,
+          value() { return []; },
+        },
+        /** @type {?number} */
+        _processHandle: Number,
+        /**
        * The promise last returned from `process()` while the asynchronous
        * processing is running - `null` otherwise. Provides a `cancel()`
        * method that rejects it with `{isCancelled: true}`.
        * @type {?Object}
        */
-      _processPromise: {
-        type: Object,
-        value: null,
-      },
-      _hljs: Object,
-    },
+        _processPromise: {
+          type: Object,
+          value: null,
+        },
+        _hljs: Object,
+      };
+    }
 
     addListener(fn) {
       this.push('_listeners', fn);
-    },
+    }
 
     removeListener(fn) {
       this._listeners = this._listeners.filter(f => f != fn);
-    },
+    }
 
     /**
      * Annotation layer method to add syntax annotations to the given element
@@ -214,14 +218,14 @@
         GrAnnotation.annotateElement(
             el, range.start, range.length, range.className);
       }
-    },
+    }
 
     _getLanguage(diffFileMetaInfo) {
       // The Gerrit API provides only content-type, but for other users of
       // gr-diff it may be more convenient to specify the language directly.
       return diffFileMetaInfo.language ||
           LANGUAGE_MAP[diffFileMetaInfo.content_type];
-    },
+    }
 
     /**
      * Start processing syntax for the loaded diff and notify layer listeners
@@ -298,7 +302,7 @@
           }));
       return this._processPromise
           .finally(() => { this._processPromise = null; });
-    },
+    }
 
     /**
      * Cancel any asynchronous syntax processing jobs.
@@ -311,13 +315,13 @@
       if (this._processPromise) {
         this._processPromise.cancel();
       }
-    },
+    }
 
     _diffChanged() {
       this._cancel();
       this._baseRanges = [];
       this._revisionRanges = [];
-    },
+    }
 
     /**
      * Take a string of HTML with the (potentially nested) syntax markers
@@ -339,7 +343,7 @@
       const ranges = this._rangesFromElement(div, 0);
       rangesCache.set(str, ranges);
       return ranges;
-    },
+    }
 
     _rangesFromElement(elem, offset) {
       let result = [];
@@ -362,7 +366,7 @@
         offset += nodeLength;
       }
       return result;
-    },
+    }
 
     /**
      * For a given state, process the syntax for the next line (or pair of
@@ -412,7 +416,7 @@
             this._rangesFromString(result.value, rangesCache));
         state.revisionContext = result.top;
       }
-    },
+    }
 
     /**
      * Ad hoc fixes for HLJS parsing bugs. Rewrite lines of code in constrained
@@ -479,7 +483,7 @@
       }
 
       return line;
-    },
+    }
 
     /**
      * Tells whether the state has exhausted its current section.
@@ -494,7 +498,7 @@
         return (!section.a || state.lineIndex >= section.a.length) &&
             (!section.b || state.lineIndex >= section.b.length);
       }
-    },
+    }
 
     /**
      * For a given state, notify layer listeners of any processed line ranges
@@ -516,18 +520,20 @@
             'right');
         state.lastNotify.right = state.lineNums.right;
       }
-    },
+    }
 
     _notifyRange(start, end, side) {
       for (const fn of this._listeners) {
         fn(start, end, side);
       }
-    },
+    }
 
     _loadHLJS() {
       return this.$.libLoader.getHLJS().then(hljs => {
         this._hljs = hljs;
       });
-    },
-  });
+    }
+  }
+
+  customElements.define(GrSyntaxLayer.is, GrSyntaxLayer);
 })();
diff --git a/polygerrit-ui/app/elements/diff/gr-syntax-layer/gr-syntax-layer_test.html b/polygerrit-ui/app/elements/diff/gr-syntax-layer/gr-syntax-layer_test.html
index 4e3492f..926a6bc 100644
--- a/polygerrit-ui/app/elements/diff/gr-syntax-layer/gr-syntax-layer_test.html
+++ b/polygerrit-ui/app/elements/diff/gr-syntax-layer/gr-syntax-layer_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-syntax-layer</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>
diff --git a/polygerrit-ui/app/elements/documentation/gr-documentation-search/gr-documentation-search.js b/polygerrit-ui/app/elements/documentation/gr-documentation-search/gr-documentation-search.js
index f850b9d..779b16f 100644
--- a/polygerrit-ui/app/elements/documentation/gr-documentation-search/gr-documentation-search.js
+++ b/polygerrit-ui/app/elements/documentation/gr-documentation-search/gr-documentation-search.js
@@ -17,50 +17,56 @@
 (function() {
   'use strict';
 
-  Polymer({
-    is: 'gr-documentation-search',
+  /**
+    * @appliesMixin Gerrit.ListViewMixin
+    */
+  class GrDocumentationSearch extends Polymer.mixinBehaviors( [
+    Gerrit.ListViewBehavior,
+  ], Polymer.GestureEventListeners(
+      Polymer.LegacyElementMixin(
+          Polymer.Element))) {
+    static get is() { return 'gr-documentation-search'; }
 
-    properties: {
+    static get properties() {
+      return {
       /**
        * URL params passed from the router.
        */
-      params: {
-        type: Object,
-        observer: '_paramsChanged',
-      },
+        params: {
+          type: Object,
+          observer: '_paramsChanged',
+        },
 
-      _path: {
-        type: String,
-        readOnly: true,
-        value: '/Documentation',
-      },
-      _documentationSearches: Array,
+        _path: {
+          type: String,
+          readOnly: true,
+          value: '/Documentation',
+        },
+        _documentationSearches: Array,
 
-      _loading: {
-        type: Boolean,
-        value: true,
-      },
-      _filter: {
-        type: String,
-        value: '',
-      },
-    },
-
-    behaviors: [
-      Gerrit.ListViewBehavior,
-    ],
+        _loading: {
+          type: Boolean,
+          value: true,
+        },
+        _filter: {
+          type: String,
+          value: '',
+        },
+      };
+    }
 
     attached() {
+      super.attached();
       this.dispatchEvent(
           new CustomEvent('title-change', {title: 'Documentation Search'}));
-    },
+    }
 
     _paramsChanged(params) {
       this._loading = true;
       this._filter = this.getFilterValue(params);
 
       return this._getDocumentationSearches(this._filter);
-    },
+    }
 
     _getDocumentationSearches(filter) {
       this._documentationSearches = [];
@@ -71,11 +77,13 @@
             this._documentationSearches = searches;
             this._loading = false;
           });
-    },
+    }
 
     _computeSearchUrl(url) {
       if (!url) { return ''; }
       return this.getBaseUrl() + '/' + url;
-    },
-  });
+    }
+  }
+
+  customElements.define(GrDocumentationSearch.is, GrDocumentationSearch);
 })();
diff --git a/polygerrit-ui/app/elements/documentation/gr-documentation-search/gr-documentation-search_test.html b/polygerrit-ui/app/elements/documentation/gr-documentation-search/gr-documentation-search_test.html
index 84298e2..688a9e6 100644
--- a/polygerrit-ui/app/elements/documentation/gr-documentation-search/gr-documentation-search_test.html
+++ b/polygerrit-ui/app/elements/documentation/gr-documentation-search/gr-documentation-search_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-documentation-search</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/page/page.js"></script>
diff --git a/polygerrit-ui/app/elements/edit/gr-default-editor/gr-default-editor.js b/polygerrit-ui/app/elements/edit/gr-default-editor/gr-default-editor.js
index ed96bb2..ce9db61 100644
--- a/polygerrit-ui/app/elements/edit/gr-default-editor/gr-default-editor.js
+++ b/polygerrit-ui/app/elements/edit/gr-default-editor/gr-default-editor.js
@@ -17,23 +17,28 @@
 (function() {
   'use strict';
 
-  Polymer({
-    is: 'gr-default-editor',
-
+  class GrDefaultEditor extends Polymer.GestureEventListeners(
+      Polymer.LegacyElementMixin(
+          Polymer.Element)) {
+    static get is() { return 'gr-default-editor'; }
     /**
      * Fired when the content of the editor changes.
      *
      * @event content-change
      */
 
-    properties: {
-      fileContent: String,
-    },
+    static get properties() {
+      return {
+        fileContent: String,
+      };
+    }
 
     _handleTextareaInput(e) {
       this.dispatchEvent(new CustomEvent(
           'content-change',
           {detail: {value: e.target.value}, bubbles: true, composed: true}));
-    },
-  });
+    }
+  }
+
+  customElements.define(GrDefaultEditor.is, GrDefaultEditor);
 })();
diff --git a/polygerrit-ui/app/elements/edit/gr-default-editor/gr-default-editor_test.html b/polygerrit-ui/app/elements/edit/gr-default-editor/gr-default-editor_test.html
index c986e7c..4635b1c 100644
--- a/polygerrit-ui/app/elements/edit/gr-default-editor/gr-default-editor_test.html
+++ b/polygerrit-ui/app/elements/edit/gr-default-editor/gr-default-editor_test.html
@@ -17,7 +17,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-default-editor</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>
diff --git a/polygerrit-ui/app/elements/edit/gr-edit-constants.html b/polygerrit-ui/app/elements/edit/gr-edit-constants.html
index d526ccd..5895124 100644
--- a/polygerrit-ui/app/elements/edit/gr-edit-constants.html
+++ b/polygerrit-ui/app/elements/edit/gr-edit-constants.html
@@ -22,7 +22,7 @@
 
     // Order corresponds to order in the UI.
     GrEditConstants.Actions = {
-      OPEN: {label: 'Open', id: 'open'},
+      OPEN: {label: 'Add/Open', id: 'open'},
       DELETE: {label: 'Delete', id: 'delete'},
       RENAME: {label: 'Rename', id: 'rename'},
       RESTORE: {label: 'Restore', id: 'restore'},
diff --git a/polygerrit-ui/app/elements/edit/gr-edit-controls/gr-edit-controls.html b/polygerrit-ui/app/elements/edit/gr-edit-controls/gr-edit-controls.html
index 52692a7..4b9fbac 100644
--- a/polygerrit-ui/app/elements/edit/gr-edit-controls/gr-edit-controls.html
+++ b/polygerrit-ui/app/elements/edit/gr-edit-controls/gr-edit-controls.html
@@ -89,12 +89,12 @@
           id="openDialog"
           class="invisible dialog"
           disabled$="[[!_isValidPath(_path)]]"
-          confirm-label="Open"
+          confirm-label="Confirm"
           confirm-on-enter
           on-confirm="_handleOpenConfirm"
           on-cancel="_handleDialogCancel">
         <div class="header" slot="header">
-          Open an existing or new file
+          Add a new file or open an existing file
         </div>
         <div class="main" slot="main">
           <gr-autocomplete
diff --git a/polygerrit-ui/app/elements/edit/gr-edit-controls/gr-edit-controls.js b/polygerrit-ui/app/elements/edit/gr-edit-controls/gr-edit-controls.js
index 32aad5a..6a585e1 100644
--- a/polygerrit-ui/app/elements/edit/gr-edit-controls/gr-edit-controls.js
+++ b/polygerrit-ui/app/elements/edit/gr-edit-controls/gr-edit-controls.js
@@ -17,47 +17,52 @@
 (function() {
   'use strict';
 
-  Polymer({
-    is: 'gr-edit-controls',
+  /**
+    * @appliesMixin Gerrit.PatchSetMixin
+    */
+  class GrEditControls extends Polymer.mixinBehaviors( [
+    Gerrit.PatchSetBehavior,
+  ], Polymer.GestureEventListeners(
+      Polymer.LegacyElementMixin(
+          Polymer.Element))) {
+    static get is() { return 'gr-edit-controls'; }
 
-    properties: {
-      change: Object,
-      patchNum: String,
+    static get properties() {
+      return {
+        change: Object,
+        patchNum: String,
 
-      /**
+        /**
        * TODO(kaspern): by default, the RESTORE action should be hidden in the
        * file-list as it is a per-file action only. Remove this default value
        * when the Actions dictionary is moved to a shared constants file and
        * use the hiddenActions property in the parent component.
        */
-      hiddenActions: {
-        type: Array,
-        value() { return [GrEditConstants.Actions.RESTORE.id]; },
-      },
-
-      _actions: {
-        type: Array,
-        value() { return Object.values(GrEditConstants.Actions); },
-      },
-      _path: {
-        type: String,
-        value: '',
-      },
-      _newPath: {
-        type: String,
-        value: '',
-      },
-      _query: {
-        type: Function,
-        value() {
-          return this._queryFiles.bind(this);
+        hiddenActions: {
+          type: Array,
+          value() { return [GrEditConstants.Actions.RESTORE.id]; },
         },
-      },
-    },
 
-    behaviors: [
-      Gerrit.PatchSetBehavior,
-    ],
+        _actions: {
+          type: Array,
+          value() { return Object.values(GrEditConstants.Actions); },
+        },
+        _path: {
+          type: String,
+          value: '',
+        },
+        _newPath: {
+          type: String,
+          value: '',
+        },
+        _query: {
+          type: Function,
+          value() {
+            return this._queryFiles.bind(this);
+          },
+        },
+      };
+    }
 
     _handleTap(e) {
       e.preventDefault();
@@ -76,7 +81,7 @@
           this.openRestoreDialog();
           return;
       }
-    },
+    }
 
     /**
      * @param {string=} opt_path
@@ -84,7 +89,7 @@
     openOpenDialog(opt_path) {
       if (opt_path) { this._path = opt_path; }
       return this._showDialog(this.$.openDialog);
-    },
+    }
 
     /**
      * @param {string=} opt_path
@@ -92,7 +97,7 @@
     openDeleteDialog(opt_path) {
       if (opt_path) { this._path = opt_path; }
       return this._showDialog(this.$.deleteDialog);
-    },
+    }
 
     /**
      * @param {string=} opt_path
@@ -100,7 +105,7 @@
     openRenameDialog(opt_path) {
       if (opt_path) { this._path = opt_path; }
       return this._showDialog(this.$.renameDialog);
-    },
+    }
 
     /**
      * @param {string=} opt_path
@@ -108,7 +113,7 @@
     openRestoreDialog(opt_path) {
       if (opt_path) { this._path = opt_path; }
       return this._showDialog(this.$.restoreDialog);
-    },
+    }
 
     /**
      * Given a path string, checks that it is a valid file path.
@@ -118,11 +123,11 @@
     _isValidPath(path) {
       // Double negation needed for strict boolean return type.
       return !!path.length && !path.endsWith('/');
-    },
+    }
 
     _computeRenameDisabled(path, newPath) {
       return this._isValidPath(path) && this._isValidPath(newPath);
-    },
+    }
 
     /**
      * Given a dom event, gets the dialog that lies along this event path.
@@ -134,7 +139,7 @@
         if (!element.classList) { return false; }
         return element.classList.contains('dialog');
       });
-    },
+    }
 
     _showDialog(dialog) {
       // Some dialogs may not fire their on-close event when closed in certain
@@ -148,12 +153,12 @@
         if (autocomplete) { autocomplete.focus(); }
         this.async(() => { this.$.overlay.center(); }, 1);
       });
-    },
+    }
 
     _hideAllDialogs() {
       const dialogs = Polymer.dom(this.root).querySelectorAll('.dialog');
       for (const dialog of dialogs) { this._closeDialog(dialog); }
-    },
+    }
 
     /**
      * @param {Element|undefined} dialog
@@ -175,18 +180,18 @@
 
       dialog.classList.toggle('invisible', true);
       return this.$.overlay.close();
-    },
+    }
 
     _handleDialogCancel(e) {
       this._closeDialog(this._getDialogFromEvent(e));
-    },
+    }
 
     _handleOpenConfirm(e) {
       const url = Gerrit.Nav.getEditUrlForDiff(this.change, this._path,
           this.patchNum);
       Gerrit.Nav.navigateToRelativeUrl(url);
       this._closeDialog(this._getDialogFromEvent(e), true);
-    },
+    }
 
     _handleDeleteConfirm(e) {
       // Get the dialog before the api call as the event will change during bubbling
@@ -198,7 +203,7 @@
             this._closeDialog(dialog, true);
             Gerrit.Nav.navigateToChange(this.change);
           });
-    },
+    }
 
     _handleRestoreConfirm(e) {
       const dialog = this._getDialogFromEvent(e);
@@ -208,7 +213,7 @@
             this._closeDialog(dialog, true);
             Gerrit.Nav.navigateToChange(this.change);
           });
-    },
+    }
 
     _handleRenameConfirm(e) {
       const dialog = this._getDialogFromEvent(e);
@@ -218,17 +223,19 @@
         this._closeDialog(dialog, true);
         Gerrit.Nav.navigateToChange(this.change);
       });
-    },
+    }
 
     _queryFiles(input) {
       return this.$.restAPI.queryChangeFiles(this.change._number,
           this.patchNum, input).then(res => res.map(file => {
         return {name: file};
       }));
-    },
+    }
 
     _computeIsInvisible(id, hiddenActions) {
       return hiddenActions.includes(id) ? 'invisible' : '';
-    },
-  });
+    }
+  }
+
+  customElements.define(GrEditControls.is, GrEditControls);
 })();
diff --git a/polygerrit-ui/app/elements/edit/gr-edit-controls/gr-edit-controls_test.html b/polygerrit-ui/app/elements/edit/gr-edit-controls/gr-edit-controls_test.html
index dd8cb74..2d7dc9c 100644
--- a/polygerrit-ui/app/elements/edit/gr-edit-controls/gr-edit-controls_test.html
+++ b/polygerrit-ui/app/elements/edit/gr-edit-controls/gr-edit-controls_test.html
@@ -17,7 +17,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-edit-controls</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>
diff --git a/polygerrit-ui/app/elements/edit/gr-edit-file-controls/gr-edit-file-controls.js b/polygerrit-ui/app/elements/edit/gr-edit-file-controls/gr-edit-file-controls.js
index 250816b..ddd2d29 100644
--- a/polygerrit-ui/app/elements/edit/gr-edit-file-controls/gr-edit-file-controls.js
+++ b/polygerrit-ui/app/elements/edit/gr-edit-file-controls/gr-edit-file-controls.js
@@ -17,38 +17,41 @@
 (function() {
   'use strict';
 
-  Polymer({
-    is: 'gr-edit-file-controls',
-
+  class GrEditFileControls extends Polymer.GestureEventListeners(
+      Polymer.LegacyElementMixin(
+          Polymer.Element)) {
+    static get is() { return 'gr-edit-file-controls'; }
     /**
      * Fired when an action in the overflow menu is tapped.
      *
      * @event file-action-tap
      */
 
-    properties: {
-      filePath: String,
-      _allFileActions: {
-        type: Array,
-        value: () => Object.values(GrEditConstants.Actions),
-      },
-      _fileActions: {
-        type: Array,
-        computed: '_computeFileActions(_allFileActions)',
-      },
-    },
+    static get properties() {
+      return {
+        filePath: String,
+        _allFileActions: {
+          type: Array,
+          value: () => Object.values(GrEditConstants.Actions),
+        },
+        _fileActions: {
+          type: Array,
+          computed: '_computeFileActions(_allFileActions)',
+        },
+      };
+    }
 
     _handleActionTap(e) {
       e.preventDefault();
       e.stopPropagation();
       this._dispatchFileAction(e.detail.id, this.filePath);
-    },
+    }
 
     _dispatchFileAction(action, path) {
       this.dispatchEvent(new CustomEvent(
           'file-action-tap',
           {detail: {action, path}, bubbles: true, composed: true}));
-    },
+    }
 
     _computeFileActions(actions) {
       // TODO(kaspern): conditionally disable some actions based on file status.
@@ -58,6 +61,8 @@
           id: action.id,
         };
       });
-    },
-  });
+    }
+  }
+
+  customElements.define(GrEditFileControls.is, GrEditFileControls);
 })();
diff --git a/polygerrit-ui/app/elements/edit/gr-edit-file-controls/gr-edit-file-controls_test.html b/polygerrit-ui/app/elements/edit/gr-edit-file-controls/gr-edit-file-controls_test.html
index 7979e57..dc26468 100644
--- a/polygerrit-ui/app/elements/edit/gr-edit-file-controls/gr-edit-file-controls_test.html
+++ b/polygerrit-ui/app/elements/edit/gr-edit-file-controls/gr-edit-file-controls_test.html
@@ -17,7 +17,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-edit-file-controls</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>
diff --git a/polygerrit-ui/app/elements/edit/gr-editor-view/gr-editor-view.js b/polygerrit-ui/app/elements/edit/gr-editor-view/gr-editor-view.js
index bbcb90c..ab50921 100644
--- a/polygerrit-ui/app/elements/edit/gr-editor-view/gr-editor-view.js
+++ b/polygerrit-ui/app/elements/edit/gr-editor-view/gr-editor-view.js
@@ -24,9 +24,21 @@
 
   const STORAGE_DEBOUNCE_INTERVAL_MS = 100;
 
-  Polymer({
-    is: 'gr-editor-view',
-
+  /**
+    * @appliesMixin Gerrit.FireMixin
+    * @appliesMixin Gerrit.KeyboardShortcutMixin
+    * @appliesMixin Gerrit.PatchSetMixin
+    * @appliesMixin Gerrit.PathListMixin
+    */
+  class GrEditorView extends Polymer.mixinBehaviors( [
+    Gerrit.FireBehavior,
+    Gerrit.KeyboardShortcutBehavior,
+    Gerrit.PatchSetBehavior,
+    Gerrit.PathListBehavior,
+  ], Polymer.GestureEventListeners(
+      Polymer.LegacyElementMixin(
+          Polymer.Element))) {
+    static get is() { return 'gr-editor-view'; }
     /**
      * Fired when the title of the page should change.
      *
@@ -39,69 +51,69 @@
      * @event show-alert
      */
 
-    properties: {
+    static get properties() {
+      return {
       /**
        * URL params passed from the router.
        */
-      params: {
-        type: Object,
-        observer: '_paramsChanged',
-      },
+        params: {
+          type: Object,
+          observer: '_paramsChanged',
+        },
 
-      _change: Object,
-      _changeEditDetail: Object,
-      _changeNum: String,
-      _patchNum: String,
-      _path: String,
-      _type: String,
-      _content: String,
-      _newContent: String,
-      _saving: {
-        type: Boolean,
-        value: false,
-      },
-      _successfulSave: {
-        type: Boolean,
-        value: false,
-      },
-      _saveDisabled: {
-        type: Boolean,
-        value: true,
-        computed: '_computeSaveDisabled(_content, _newContent, _saving)',
-      },
-      _prefs: Object,
-    },
+        _change: Object,
+        _changeEditDetail: Object,
+        _changeNum: String,
+        _patchNum: String,
+        _path: String,
+        _type: String,
+        _content: String,
+        _newContent: String,
+        _saving: {
+          type: Boolean,
+          value: false,
+        },
+        _successfulSave: {
+          type: Boolean,
+          value: false,
+        },
+        _saveDisabled: {
+          type: Boolean,
+          value: true,
+          computed: '_computeSaveDisabled(_content, _newContent, _saving)',
+        },
+        _prefs: Object,
+      };
+    }
 
-    behaviors: [
-      Gerrit.FireBehavior,
-      Gerrit.KeyboardShortcutBehavior,
-      Gerrit.PatchSetBehavior,
-      Gerrit.PathListBehavior,
-    ],
+    get keyBindings() {
+      return {
+        'ctrl+s meta+s': '_handleSaveShortcut',
+      };
+    }
 
-    listeners: {
-      'content-change': '_handleContentChange',
-    },
-
-    keyBindings: {
-      'ctrl+s meta+s': '_handleSaveShortcut',
-    },
+    created() {
+      super.created();
+      this.addEventListener('content-change',
+          e => this._handleContentChange(e));
+    }
 
     attached() {
+      super.attached();
       this._getEditPrefs().then(prefs => { this._prefs = prefs; });
-    },
+    }
 
     get storageKey() {
       return `c${this._changeNum}_ps${this._patchNum}_${this._path}`;
-    },
+    }
 
     _getLoggedIn() {
       return this.$.restAPI.getLoggedIn();
-    },
+    }
 
     _getEditPrefs() {
       return this.$.restAPI.getEditPreferences();
-    },
+    }
 
     _paramsChanged(value) {
       if (value.view !== Gerrit.Nav.View.EDIT) {
@@ -126,13 +138,13 @@
       promises.push(
           this._getFileData(this._changeNum, this._path, this._patchNum));
       return Promise.all(promises);
-    },
+    }
 
     _getChangeDetail(changeNum) {
       return this.$.restAPI.getDiffChangeDetail(changeNum).then(change => {
         this._change = change;
       });
-    },
+    }
 
     _handlePathChanged(e) {
       const path = e.detail;
@@ -146,13 +158,13 @@
         this._successfulSave = true;
         this._viewEditInChangeView();
       });
-    },
+    }
 
     _viewEditInChangeView() {
       const patch = this._successfulSave ? this.EDIT_NAME : this._patchNum;
       Gerrit.Nav.navigateToChange(this._change, patch, null,
           patch !== this.EDIT_NAME);
-    },
+    }
 
     _getFileData(changeNum, path, patchNum) {
       const storedContent =
@@ -183,7 +195,7 @@
               this._type = '';
             }
           });
-    },
+    }
 
     _saveEdit() {
       this._saving = true;
@@ -198,7 +210,7 @@
         this._content = this._newContent;
         this._successfulSave = true;
       });
-    },
+    }
 
     _showAlert(message) {
       this.dispatchEvent(new CustomEvent('show-alert', {
@@ -206,7 +218,7 @@
         bubbles: true,
         composed: true,
       }));
-    },
+    }
 
     _computeSaveDisabled(content, newContent, saving) {
       // Polymer 2: check for undefined
@@ -222,12 +234,12 @@
         return true;
       }
       return content === newContent;
-    },
+    }
 
     _handleCloseTap() {
       // TODO(kaspern): Add a confirm dialog if there are unsaved changes.
       this._viewEditInChangeView();
-    },
+    }
 
     _handleContentChange(e) {
       this.debounce('store', () => {
@@ -239,13 +251,15 @@
           this.$.storage.eraseEditableContentItem(this.storageKey);
         }
       }, STORAGE_DEBOUNCE_INTERVAL_MS);
-    },
+    }
 
     _handleSaveShortcut(e) {
       e.preventDefault();
       if (!this._saveDisabled) {
         this._saveEdit();
       }
-    },
-  });
+    }
+  }
+
+  customElements.define(GrEditorView.is, GrEditorView);
 })();
diff --git a/polygerrit-ui/app/elements/edit/gr-editor-view/gr-editor-view_test.html b/polygerrit-ui/app/elements/edit/gr-editor-view/gr-editor-view_test.html
index 226472f..ebd624e 100644
--- a/polygerrit-ui/app/elements/edit/gr-editor-view/gr-editor-view_test.html
+++ b/polygerrit-ui/app/elements/edit/gr-editor-view/gr-editor-view_test.html
@@ -17,7 +17,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-editor-view</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>
diff --git a/polygerrit-ui/app/elements/gr-app-element.js b/polygerrit-ui/app/elements/gr-app-element.js
index b8f789b..4ed0d3a 100644
--- a/polygerrit-ui/app/elements/gr-app-element.js
+++ b/polygerrit-ui/app/elements/gr-app-element.js
@@ -17,90 +17,90 @@
 (function() {
   'use strict';
 
-  Polymer({
-    is: 'gr-app-element',
-
+  /**
+    * @appliesMixin Gerrit.BaseUrlMixin
+    * @appliesMixin Gerrit.KeyboardShortcutMixin
+    */
+  class GrAppElement extends Polymer.mixinBehaviors( [
+    Gerrit.BaseUrlBehavior,
+    Gerrit.KeyboardShortcutBehavior,
+  ], Polymer.GestureEventListeners(
+      Polymer.LegacyElementMixin(
+          Polymer.Element))) {
+    static get is() { return 'gr-app-element'; }
     /**
      * Fired when the URL location changes.
      *
      * @event location-change
      */
 
-    properties: {
+    static get properties() {
+      return {
       /**
        * @type {{ query: string, view: string, screen: string }}
        */
-      params: Object,
-      keyEventTarget: {
-        type: Object,
-        value() { return document.body; },
-      },
+        params: Object,
+        keyEventTarget: {
+          type: Object,
+          value() { return document.body; },
+        },
 
-      _account: {
-        type: Object,
-        observer: '_accountChanged',
-      },
+        _account: {
+          type: Object,
+          observer: '_accountChanged',
+        },
 
-      /**
+        /**
        * The last time the g key was pressed in milliseconds (or a keydown event
        * was handled if the key is held down).
        * @type {number|null}
        */
-      _lastGKeyPressTimestamp: {
-        type: Number,
-        value: null,
-      },
+        _lastGKeyPressTimestamp: {
+          type: Number,
+          value: null,
+        },
 
-      /**
+        /**
        * @type {{ plugin: Object }}
        */
-      _serverConfig: Object,
-      _version: String,
-      _showChangeListView: Boolean,
-      _showDashboardView: Boolean,
-      _showChangeView: Boolean,
-      _showDiffView: Boolean,
-      _showSettingsView: Boolean,
-      _showAdminView: Boolean,
-      _showCLAView: Boolean,
-      _showEditorView: Boolean,
-      _showPluginScreen: Boolean,
-      _showDocumentationSearch: Boolean,
-      /** @type {?} */
-      _viewState: Object,
-      /** @type {?} */
-      _lastError: Object,
-      _lastSearchPage: String,
-      _path: String,
-      _pluginScreenName: {
-        type: String,
-        computed: '_computePluginScreenName(params)',
-      },
-      _settingsUrl: String,
-      _feedbackUrl: String,
-      // Used to allow searching on mobile
-      mobileSearch: {
-        type: Boolean,
-        value: false,
-      },
-    },
+        _serverConfig: Object,
+        _version: String,
+        _showChangeListView: Boolean,
+        _showDashboardView: Boolean,
+        _showChangeView: Boolean,
+        _showDiffView: Boolean,
+        _showSettingsView: Boolean,
+        _showAdminView: Boolean,
+        _showCLAView: Boolean,
+        _showEditorView: Boolean,
+        _showPluginScreen: Boolean,
+        _showDocumentationSearch: Boolean,
+        /** @type {?} */
+        _viewState: Object,
+        /** @type {?} */
+        _lastError: Object,
+        _lastSearchPage: String,
+        _path: String,
+        _pluginScreenName: {
+          type: String,
+          computed: '_computePluginScreenName(params)',
+        },
+        _settingsUrl: String,
+        _feedbackUrl: String,
+        // Used to allow searching on mobile
+        mobileSearch: {
+          type: Boolean,
+          value: false,
+        },
+      };
+    }
 
-    listeners: {
-      'page-error': '_handlePageError',
-      'title-change': '_handleTitleChange',
-      'location-change': '_handleLocationChange',
-      'rpc-log': '_handleRpcLog',
-    },
-
-    observers: [
-      '_viewChanged(params.view)',
-      '_paramsChanged(params.*)',
-    ],
-
-    behaviors: [
-      Gerrit.BaseUrlBehavior,
-      Gerrit.KeyboardShortcutBehavior,
-    ],
+    static get observers() {
+      return [
+        '_viewChanged(params.view)',
+        '_paramsChanged(params.*)',
+      ];
+    }
 
     keyboardShortcuts() {
       return {
@@ -111,13 +111,23 @@
         [this.Shortcut.GO_TO_ABANDONED_CHANGES]: '_goToAbandonedChanges',
         [this.Shortcut.GO_TO_WATCHED_CHANGES]: '_goToWatchedChanges',
       };
-    },
+    }
 
     created() {
+      super.created();
       this._bindKeyboardShortcuts();
-    },
+      this.addEventListener('page-error',
+          e => this._handlePageError(e));
+      this.addEventListener('title-change',
+          e => this._handleTitleChange(e));
+      this.addEventListener('location-change',
+          e => this._handleLocationChange(e));
+      this.addEventListener('rpc-log',
+          e => this._handleRpcLog(e));
+    }
 
     ready() {
+      super.ready();
       this.$.reporting.appStarted();
       this.$.router.start();
 
@@ -165,7 +175,7 @@
           selectedChangeIndex: 0,
         },
       };
-    },
+    }
 
     _bindKeyboardShortcuts() {
       this.bindShortcut(this.Shortcut.SEND_REPLY,
@@ -287,7 +297,7 @@
 
       this.bindShortcut(
           this.Shortcut.SEARCH, '/');
-    },
+    }
 
     _accountChanged(account) {
       if (!account) { return; }
@@ -298,7 +308,7 @@
       this.$.restAPI.getEditPreferences();
       this.$.errorManager.knownAccountId =
           this._account && this._account._account_id || null;
-    },
+    }
 
     _viewChanged(view) {
       this.$.errorView.classList.remove('show');
@@ -328,7 +338,7 @@
         });
       }
       this.$.header.unfloat();
-    },
+    }
 
     _handlePageError(e) {
       const props = [
@@ -356,7 +366,7 @@
           this._lastError = err;
         });
       }
-    },
+    }
 
     _handleLocationChange(e) {
       const hash = e.detail.hash.substring(1);
@@ -365,7 +375,7 @@
         pathname += '@' + hash;
       }
       this.set('_path', pathname);
-    },
+    }
 
     _paramsChanged(paramsRecord) {
       const params = paramsRecord.base;
@@ -373,7 +383,7 @@
       if (viewsToCheck.includes(params.view)) {
         this.set('_lastSearchPage', location.pathname);
       }
-    },
+    }
 
     _handleTitleChange(e) {
       if (e.detail.title) {
@@ -381,54 +391,54 @@
       } else {
         document.title = '';
       }
-    },
+    }
 
     _showKeyboardShortcuts(e) {
       if (this.shouldSuppressKeyboardShortcut(e)) { return; }
       this.$.keyboardShortcuts.open();
-    },
+    }
 
     _handleKeyboardShortcutDialogClose() {
       this.$.keyboardShortcuts.close();
-    },
+    }
 
     _handleAccountDetailUpdate(e) {
       this.$.mainHeader.reload();
       if (this.params.view === Gerrit.Nav.View.SETTINGS) {
         this.$$('gr-settings-view').reloadAccountDetail();
       }
-    },
+    }
 
     _handleRegistrationDialogClose(e) {
       this.params.justRegistered = false;
       this.$.registrationOverlay.close();
-    },
+    }
 
     _goToOpenedChanges() {
       Gerrit.Nav.navigateToStatusSearch('open');
-    },
+    }
 
     _goToUserDashboard() {
       Gerrit.Nav.navigateToUserDashboard();
-    },
+    }
 
     _goToMergedChanges() {
       Gerrit.Nav.navigateToStatusSearch('merged');
-    },
+    }
 
     _goToAbandonedChanges() {
       Gerrit.Nav.navigateToStatusSearch('abandoned');
-    },
+    }
 
     _goToWatchedChanges() {
       // The query is hardcoded, and doesn't respect custom menu entries
       Gerrit.Nav.navigateToSearchQuery('is:watched is:open');
-    },
+    }
 
     _computePluginScreenName({plugin, screen}) {
       if (!plugin || !screen) return '';
       return `${plugin}-screen-${screen}`;
-    },
+    }
 
     _logWelcome() {
       console.group('Runtime Info');
@@ -441,7 +451,7 @@
         console.log(`Please file bugs and feedback at: ${this._feedbackUrl}`);
       }
       console.groupEnd();
-    },
+    }
 
     /**
      * Intercept RPC log events emitted by REST API interfaces.
@@ -451,17 +461,19 @@
     _handleRpcLog(e) {
       this.$.reporting.reportRpcTiming(e.detail.anonymizedUrl,
           e.detail.elapsed);
-    },
+    }
 
     _mobileSearchToggle(e) {
       this.mobileSearch = !this.mobileSearch;
-    },
+    }
 
     getThemeEndpoint() {
       // For now, we only have dark mode and light mode
       return window.localStorage.getItem('dark-theme') ?
         'app-theme-dark' :
         'app-theme-light';
-    },
-  });
+    }
+  }
+
+  customElements.define(GrAppElement.is, GrAppElement);
 })();
diff --git a/polygerrit-ui/app/elements/gr-app.js b/polygerrit-ui/app/elements/gr-app.js
index ac8ea1a..46a81ee 100644
--- a/polygerrit-ui/app/elements/gr-app.js
+++ b/polygerrit-ui/app/elements/gr-app.js
@@ -17,7 +17,11 @@
 (function() {
   'use strict';
 
-  Polymer({
-    is: 'gr-app',
-  });
+  class GrApp extends Polymer.GestureEventListeners(
+      Polymer.LegacyElementMixin(
+          Polymer.Element)) {
+    static get is() { return 'gr-app'; }
+  }
+
+  customElements.define(GrApp.is, GrApp);
 })();
diff --git a/polygerrit-ui/app/elements/gr-app_test.html b/polygerrit-ui/app/elements/gr-app_test.html
index 9f1b7f8..c069f8b 100644
--- a/polygerrit-ui/app/elements/gr-app_test.html
+++ b/polygerrit-ui/app/elements/gr-app_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-app</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>
diff --git a/polygerrit-ui/app/elements/plugins/gr-admin-api/gr-admin-api_test.html b/polygerrit-ui/app/elements/plugins/gr-admin-api/gr-admin-api_test.html
index 2537a37..10ba710 100644
--- a/polygerrit-ui/app/elements/plugins/gr-admin-api/gr-admin-api_test.html
+++ b/polygerrit-ui/app/elements/plugins/gr-admin-api/gr-admin-api_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-admin-api</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>
diff --git a/polygerrit-ui/app/elements/plugins/gr-attribute-helper/gr-attribute-helper_test.html b/polygerrit-ui/app/elements/plugins/gr-attribute-helper/gr-attribute-helper_test.html
index 0c4149c..c95128b 100644
--- a/polygerrit-ui/app/elements/plugins/gr-attribute-helper/gr-attribute-helper_test.html
+++ b/polygerrit-ui/app/elements/plugins/gr-attribute-helper/gr-attribute-helper_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-attribute-helper</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>
diff --git a/polygerrit-ui/app/elements/plugins/gr-dom-hooks/gr-dom-hooks_test.html b/polygerrit-ui/app/elements/plugins/gr-dom-hooks/gr-dom-hooks_test.html
index 9e657fa..a00dc68 100644
--- a/polygerrit-ui/app/elements/plugins/gr-dom-hooks/gr-dom-hooks_test.html
+++ b/polygerrit-ui/app/elements/plugins/gr-dom-hooks/gr-dom-hooks_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-dom-hooks</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>
diff --git a/polygerrit-ui/app/elements/plugins/gr-endpoint-decorator/gr-endpoint-decorator.js b/polygerrit-ui/app/elements/plugins/gr-endpoint-decorator/gr-endpoint-decorator.js
index ba49291..9942716 100644
--- a/polygerrit-ui/app/elements/plugins/gr-endpoint-decorator/gr-endpoint-decorator.js
+++ b/polygerrit-ui/app/elements/plugins/gr-endpoint-decorator/gr-endpoint-decorator.js
@@ -19,33 +19,38 @@
 
   const INIT_PROPERTIES_TIMEOUT_MS = 10000;
 
-  Polymer({
-    is: 'gr-endpoint-decorator',
+  class GrEndpointDecorator extends Polymer.GestureEventListeners(
+      Polymer.LegacyElementMixin(
+          Polymer.Element)) {
+    static get is() { return 'gr-endpoint-decorator'; }
 
-    properties: {
-      name: String,
-      /** @type {!Map} */
-      _domHooks: {
-        type: Map,
-        value() { return new Map(); },
-      },
-      /**
+    static get properties() {
+      return {
+        name: String,
+        /** @type {!Map} */
+        _domHooks: {
+          type: Map,
+          value() { return new Map(); },
+        },
+        /**
        * This map prevents importing the same endpoint twice.
        * Without caching, if a plugin is loaded after the loaded plugins
        * callback fires, it will be imported twice and appear twice on the page.
        * @type {!Map}
        */
-      _initializedPlugins: {
-        type: Map,
-        value() { return new Map(); },
-      },
-    },
+        _initializedPlugins: {
+          type: Map,
+          value() { return new Map(); },
+        },
+      };
+    }
 
     detached() {
+      super.detached();
       for (const [el, domHook] of this._domHooks) {
         domHook.handleInstanceDetached(el);
       }
-    },
+    }
 
     /**
      * @suppress {checkTypes}
@@ -54,7 +59,7 @@
       return new Promise((resolve, reject) => {
         (this.importHref || Polymer.importHref)(url, resolve, reject);
       });
-    },
+    }
 
     _initDecoration(name, plugin) {
       const el = document.createElement(name);
@@ -62,7 +67,7 @@
           this.getContentChildren().find(
               el => el.nodeName !== 'GR-ENDPOINT-PARAM'))
           .then(el => this._appendChild(el));
-    },
+    }
 
     _initReplacement(name, plugin) {
       this.getContentChildNodes()
@@ -71,12 +76,12 @@
       const el = document.createElement(name);
       return this._initProperties(el, plugin).then(
           el => this._appendChild(el));
-    },
+    }
 
     _getEndpointParams() {
       return Array.from(
           Polymer.dom(this).querySelectorAll('gr-endpoint-param'));
-    },
+    }
 
     /**
      * @param {!Element} el
@@ -109,11 +114,11 @@
             clearTimeout(timeoutId);
             return el;
           });
-    },
+    }
 
     _appendChild(el) {
       return Polymer.dom(this.root).appendChild(el);
-    },
+    }
 
     _initModule({moduleName, plugin, type, domHook}) {
       const name = plugin.getPluginName() + '.' + moduleName;
@@ -137,9 +142,10 @@
         domHook.handleInstanceAttached(el);
         this._domHooks.set(el, domHook);
       });
-    },
+    }
 
     ready() {
+      super.ready();
       Gerrit._endpoints.onNewEndpoint(this.name, this._initModule.bind(this));
       Gerrit.awaitPluginsLoaded().then(() => Promise.all(
           Gerrit._endpoints.getPlugins(this.name).map(
@@ -149,6 +155,8 @@
             .getDetails(this.name)
             .forEach(this._initModule, this)
       );
-    },
-  });
+    }
+  }
+
+  customElements.define(GrEndpointDecorator.is, GrEndpointDecorator);
 })();
diff --git a/polygerrit-ui/app/elements/plugins/gr-endpoint-decorator/gr-endpoint-decorator_test.html b/polygerrit-ui/app/elements/plugins/gr-endpoint-decorator/gr-endpoint-decorator_test.html
index b0ad585..58e2074 100644
--- a/polygerrit-ui/app/elements/plugins/gr-endpoint-decorator/gr-endpoint-decorator_test.html
+++ b/polygerrit-ui/app/elements/plugins/gr-endpoint-decorator/gr-endpoint-decorator_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-endpoint-decorator</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>
diff --git a/polygerrit-ui/app/elements/plugins/gr-endpoint-param/gr-endpoint-param.js b/polygerrit-ui/app/elements/plugins/gr-endpoint-param/gr-endpoint-param.js
index c7a2d9a..3010750 100644
--- a/polygerrit-ui/app/elements/plugins/gr-endpoint-param/gr-endpoint-param.js
+++ b/polygerrit-ui/app/elements/plugins/gr-endpoint-param/gr-endpoint-param.js
@@ -17,17 +17,21 @@
 (function() {
   'use strict';
 
-  Polymer({
-    is: 'gr-endpoint-param',
+  class GrEndpointParam extends Polymer.GestureEventListeners(
+      Polymer.LegacyElementMixin(
+          Polymer.Element)) {
+    static get is() { return 'gr-endpoint-param'; }
 
-    properties: {
-      name: String,
-      value: {
-        type: Object,
-        notify: true,
-        observer: '_valueChanged',
-      },
-    },
+    static get properties() {
+      return {
+        name: String,
+        value: {
+          type: Object,
+          notify: true,
+          observer: '_valueChanged',
+        },
+      };
+    }
 
     _valueChanged(newValue, oldValue) {
       /* In polymer 2 the following change was made:
@@ -42,6 +46,8 @@
         value: newValue,
       };
       this.dispatchEvent(new CustomEvent('value-changed', {detail}));
-    },
-  });
+    }
+  }
+
+  customElements.define(GrEndpointParam.is, GrEndpointParam);
 })();
diff --git a/polygerrit-ui/app/elements/plugins/gr-event-helper/gr-event-helper_test.html b/polygerrit-ui/app/elements/plugins/gr-event-helper/gr-event-helper_test.html
index bd76bd4..a98a0d2 100644
--- a/polygerrit-ui/app/elements/plugins/gr-event-helper/gr-event-helper_test.html
+++ b/polygerrit-ui/app/elements/plugins/gr-event-helper/gr-event-helper_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-event-helper</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>
diff --git a/polygerrit-ui/app/elements/plugins/gr-external-style/gr-external-style.js b/polygerrit-ui/app/elements/plugins/gr-external-style/gr-external-style.js
index acc1ac2..aa9b13e 100644
--- a/polygerrit-ui/app/elements/plugins/gr-external-style/gr-external-style.js
+++ b/polygerrit-ui/app/elements/plugins/gr-external-style/gr-external-style.js
@@ -17,20 +17,24 @@
 (function() {
   'use strict';
 
-  Polymer({
-    is: 'gr-external-style',
+  class GrExternalStyle extends Polymer.GestureEventListeners(
+      Polymer.LegacyElementMixin(
+          Polymer.Element)) {
+    static get is() { return 'gr-external-style'; }
 
-    properties: {
-      name: String,
-      _urlsImported: {
-        type: Array,
-        value() { return []; },
-      },
-      _stylesApplied: {
-        type: Array,
-        value() { return []; },
-      },
-    },
+    static get properties() {
+      return {
+        name: String,
+        _urlsImported: {
+          type: Array,
+          value() { return []; },
+        },
+        _stylesApplied: {
+          type: Array,
+          value() { return []; },
+        },
+      };
+    }
 
     /**
      * @suppress {checkTypes}
@@ -41,7 +45,7 @@
       return new Promise((resolve, reject) => {
         (this.importHref || Polymer.importHref)(url, resolve, reject);
       });
-    },
+    }
 
     _applyStyle(name) {
       if (this._stylesApplied.includes(name)) { return; }
@@ -56,7 +60,7 @@
       const topEl = document.getElementsByTagName('body')[0];
       topEl.insertBefore(cs, topEl.firstChild);
       Polymer.updateStyles();
-    },
+    }
 
     _importAndApply() {
       Promise.all(Gerrit._endpoints.getPlugins(this.name).map(
@@ -67,14 +71,18 @@
           this._applyStyle(name);
         }
       });
-    },
+    }
 
     attached() {
+      super.attached();
       this._importAndApply();
-    },
+    }
 
     ready() {
+      super.ready();
       Gerrit.awaitPluginsLoaded().then(() => this._importAndApply());
-    },
-  });
+    }
+  }
+
+  customElements.define(GrExternalStyle.is, GrExternalStyle);
 })();
diff --git a/polygerrit-ui/app/elements/plugins/gr-external-style/gr-external-style_test.html b/polygerrit-ui/app/elements/plugins/gr-external-style/gr-external-style_test.html
index 9566067..9f0c950 100644
--- a/polygerrit-ui/app/elements/plugins/gr-external-style/gr-external-style_test.html
+++ b/polygerrit-ui/app/elements/plugins/gr-external-style/gr-external-style_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-external-style</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>
diff --git a/polygerrit-ui/app/elements/plugins/gr-plugin-host/gr-plugin-host.js b/polygerrit-ui/app/elements/plugins/gr-plugin-host/gr-plugin-host.js
index 21da106..30adcc8 100644
--- a/polygerrit-ui/app/elements/plugins/gr-plugin-host/gr-plugin-host.js
+++ b/polygerrit-ui/app/elements/plugins/gr-plugin-host/gr-plugin-host.js
@@ -17,15 +17,19 @@
 (function() {
   'use strict';
 
-  Polymer({
-    is: 'gr-plugin-host',
+  class GrPluginHost extends Polymer.GestureEventListeners(
+      Polymer.LegacyElementMixin(
+          Polymer.Element)) {
+    static get is() { return 'gr-plugin-host'; }
 
-    properties: {
-      config: {
-        type: Object,
-        observer: '_configChanged',
-      },
-    },
+    static get properties() {
+      return {
+        config: {
+          type: Object,
+          observer: '_configChanged',
+        },
+      };
+    }
 
     _configChanged(config) {
       const plugins = config.plugin;
@@ -50,7 +54,7 @@
       }
 
       Gerrit._loadPlugins(pluginsPending, pluginOpts);
-    },
+    }
 
     /**
      * Omit .js plugins that have .html counterparts.
@@ -61,6 +65,8 @@
         const counterpart = url.replace(/\.js$/, '.html');
         return !htmlPlugins.includes(counterpart);
       });
-    },
-  });
+    }
+  }
+
+  customElements.define(GrPluginHost.is, GrPluginHost);
 })();
diff --git a/polygerrit-ui/app/elements/plugins/gr-plugin-host/gr-plugin-host_test.html b/polygerrit-ui/app/elements/plugins/gr-plugin-host/gr-plugin-host_test.html
index 3a8e4d8..1f748b8 100644
--- a/polygerrit-ui/app/elements/plugins/gr-plugin-host/gr-plugin-host_test.html
+++ b/polygerrit-ui/app/elements/plugins/gr-plugin-host/gr-plugin-host_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-plugin-host</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>
diff --git a/polygerrit-ui/app/elements/plugins/gr-popup-interface/gr-plugin-popup.js b/polygerrit-ui/app/elements/plugins/gr-popup-interface/gr-plugin-popup.js
index 2e7a2b7..412e788a 100644
--- a/polygerrit-ui/app/elements/plugins/gr-popup-interface/gr-plugin-popup.js
+++ b/polygerrit-ui/app/elements/plugins/gr-popup-interface/gr-plugin-popup.js
@@ -17,19 +17,23 @@
 (function(window) {
   'use strict';
 
-  Polymer({
-    is: 'gr-plugin-popup',
+  class GrPluginPopup extends Polymer.GestureEventListeners(
+      Polymer.LegacyElementMixin(
+          Polymer.Element)) {
+    static get is() { return 'gr-plugin-popup'; }
 
     get opened() {
       return this.$.overlay.opened;
-    },
+    }
 
     open() {
       return this.$.overlay.open();
-    },
+    }
 
     close() {
       this.$.overlay.close();
-    },
-  });
+    }
+  }
+
+  customElements.define(GrPluginPopup.is, GrPluginPopup);
 })(window);
diff --git a/polygerrit-ui/app/elements/plugins/gr-popup-interface/gr-plugin-popup_test.html b/polygerrit-ui/app/elements/plugins/gr-popup-interface/gr-plugin-popup_test.html
index 1f1e81e..2ff2aba 100644
--- a/polygerrit-ui/app/elements/plugins/gr-popup-interface/gr-plugin-popup_test.html
+++ b/polygerrit-ui/app/elements/plugins/gr-popup-interface/gr-plugin-popup_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-plugin-popup</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>
diff --git a/polygerrit-ui/app/elements/plugins/gr-popup-interface/gr-popup-interface_test.html b/polygerrit-ui/app/elements/plugins/gr-popup-interface/gr-popup-interface_test.html
index 53370e2..0be07d4 100644
--- a/polygerrit-ui/app/elements/plugins/gr-popup-interface/gr-popup-interface_test.html
+++ b/polygerrit-ui/app/elements/plugins/gr-popup-interface/gr-popup-interface_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-popup-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>
diff --git a/polygerrit-ui/app/elements/plugins/gr-repo-api/gr-repo-api_test.html b/polygerrit-ui/app/elements/plugins/gr-repo-api/gr-repo-api_test.html
index 0b32f8a..5a47a85 100644
--- a/polygerrit-ui/app/elements/plugins/gr-repo-api/gr-repo-api_test.html
+++ b/polygerrit-ui/app/elements/plugins/gr-repo-api/gr-repo-api_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-repo-api</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>
diff --git a/polygerrit-ui/app/elements/plugins/gr-settings-api/gr-settings-api_test.html b/polygerrit-ui/app/elements/plugins/gr-settings-api/gr-settings-api_test.html
index cbc2de6..6248d78 100644
--- a/polygerrit-ui/app/elements/plugins/gr-settings-api/gr-settings-api_test.html
+++ b/polygerrit-ui/app/elements/plugins/gr-settings-api/gr-settings-api_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-settings-api</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>
diff --git a/polygerrit-ui/app/elements/plugins/gr-styles-api/gr-styles-api_test.html b/polygerrit-ui/app/elements/plugins/gr-styles-api/gr-styles-api_test.html
index 16b7eb8..7d14e21 100644
--- a/polygerrit-ui/app/elements/plugins/gr-styles-api/gr-styles-api_test.html
+++ b/polygerrit-ui/app/elements/plugins/gr-styles-api/gr-styles-api_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-admin-api</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>
diff --git a/polygerrit-ui/app/elements/plugins/gr-theme-api/gr-theme-api_test.html b/polygerrit-ui/app/elements/plugins/gr-theme-api/gr-theme-api_test.html
index 6332b91..9401a15 100644
--- a/polygerrit-ui/app/elements/plugins/gr-theme-api/gr-theme-api_test.html
+++ b/polygerrit-ui/app/elements/plugins/gr-theme-api/gr-theme-api_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-theme-api</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>
diff --git a/polygerrit-ui/app/elements/settings/gr-account-info/gr-account-info.js b/polygerrit-ui/app/elements/settings/gr-account-info/gr-account-info.js
index 3ba3a80..e509b8b 100644
--- a/polygerrit-ui/app/elements/settings/gr-account-info/gr-account-info.js
+++ b/polygerrit-ui/app/elements/settings/gr-account-info/gr-account-info.js
@@ -17,65 +17,71 @@
 (function() {
   'use strict';
 
-  Polymer({
-    is: 'gr-account-info',
-
+  /**
+    * @appliesMixin Gerrit.FireMixin
+    */
+  class GrAccountInfo extends Polymer.mixinBehaviors( [
+    Gerrit.FireBehavior,
+  ], Polymer.GestureEventListeners(
+      Polymer.LegacyElementMixin(
+          Polymer.Element))) {
+    static get is() { return 'gr-account-info'; }
     /**
      * Fired when account details are changed.
      *
      * @event account-detail-update
      */
 
-    properties: {
-      usernameMutable: {
-        type: Boolean,
-        notify: true,
-        computed: '_computeUsernameMutable(_serverConfig, _account.username)',
-      },
-      nameMutable: {
-        type: Boolean,
-        notify: true,
-        computed: '_computeNameMutable(_serverConfig)',
-      },
-      hasUnsavedChanges: {
-        type: Boolean,
-        notify: true,
-        computed: '_computeHasUnsavedChanges(_hasNameChange, ' +
+    static get properties() {
+      return {
+        usernameMutable: {
+          type: Boolean,
+          notify: true,
+          computed: '_computeUsernameMutable(_serverConfig, _account.username)',
+        },
+        nameMutable: {
+          type: Boolean,
+          notify: true,
+          computed: '_computeNameMutable(_serverConfig)',
+        },
+        hasUnsavedChanges: {
+          type: Boolean,
+          notify: true,
+          computed: '_computeHasUnsavedChanges(_hasNameChange, ' +
             '_hasUsernameChange, _hasStatusChange)',
-      },
+        },
 
-      _hasNameChange: Boolean,
-      _hasUsernameChange: Boolean,
-      _hasStatusChange: Boolean,
-      _loading: {
-        type: Boolean,
-        value: false,
-      },
-      _saving: {
-        type: Boolean,
-        value: false,
-      },
-      /** @type {?} */
-      _account: Object,
-      _serverConfig: Object,
-      _username: {
-        type: String,
-        observer: '_usernameChanged',
-      },
-      _avatarChangeUrl: {
-        type: String,
-        value: '',
-      },
-    },
+        _hasNameChange: Boolean,
+        _hasUsernameChange: Boolean,
+        _hasStatusChange: Boolean,
+        _loading: {
+          type: Boolean,
+          value: false,
+        },
+        _saving: {
+          type: Boolean,
+          value: false,
+        },
+        /** @type {?} */
+        _account: Object,
+        _serverConfig: Object,
+        _username: {
+          type: String,
+          observer: '_usernameChanged',
+        },
+        _avatarChangeUrl: {
+          type: String,
+          value: '',
+        },
+      };
+    }
 
-    behaviors: [
-      Gerrit.FireBehavior,
-    ],
-
-    observers: [
-      '_nameChanged(_account.name)',
-      '_statusChanged(_account.status)',
-    ],
+    static get observers() {
+      return [
+        '_nameChanged(_account.name)',
+        '_statusChanged(_account.status)',
+      ];
+    }
 
     loadData() {
       const promises = [];
@@ -104,7 +110,7 @@
       return Promise.all(promises).then(() => {
         this._loading = false;
       });
-    },
+    }
 
     save() {
       if (!this.hasUnsavedChanges) {
@@ -123,29 +129,29 @@
             this._saving = false;
             this.fire('account-detail-update');
           });
-    },
+    }
 
     _maybeSetName() {
       return this._hasNameChange && this.nameMutable ?
         this.$.restAPI.setAccountName(this._account.name) :
         Promise.resolve();
-    },
+    }
 
     _maybeSetUsername() {
       return this._hasUsernameChange && this.usernameMutable ?
         this.$.restAPI.setAccountUsername(this._username) :
         Promise.resolve();
-    },
+    }
 
     _maybeSetStatus() {
       return this._hasStatusChange ?
         this.$.restAPI.setAccountStatus(this._account.status) :
         Promise.resolve();
-    },
+    }
 
     _computeHasUnsavedChanges(nameChanged, usernameChanged, statusChanged) {
       return nameChanged || usernameChanged || statusChanged;
-    },
+    }
 
     _computeUsernameMutable(config, username) {
       // Polymer 2: check for undefined
@@ -159,34 +165,34 @@
       // Username may not be changed once it is set.
       return config.auth.editable_account_fields.includes('USER_NAME') &&
           !username;
-    },
+    }
 
     _computeNameMutable(config) {
       return config.auth.editable_account_fields.includes('FULL_NAME');
-    },
+    }
 
     _statusChanged() {
       if (this._loading) { return; }
       this._hasStatusChange = true;
-    },
+    }
 
     _usernameChanged() {
       if (this._loading || !this._account) { return; }
       this._hasUsernameChange =
           (this._account.username || '') !== (this._username || '');
-    },
+    }
 
     _nameChanged() {
       if (this._loading) { return; }
       this._hasNameChange = true;
-    },
+    }
 
     _handleKeydown(e) {
       if (e.keyCode === 13) { // Enter
         e.stopPropagation();
         this.save();
       }
-    },
+    }
 
     _hideAvatarChangeUrl(avatarChangeUrl) {
       if (!avatarChangeUrl) {
@@ -194,6 +200,8 @@
       }
 
       return '';
-    },
-  });
+    }
+  }
+
+  customElements.define(GrAccountInfo.is, GrAccountInfo);
 })();
diff --git a/polygerrit-ui/app/elements/settings/gr-account-info/gr-account-info_test.html b/polygerrit-ui/app/elements/settings/gr-account-info/gr-account-info_test.html
index a35c1f0..a9056b91 100644
--- a/polygerrit-ui/app/elements/settings/gr-account-info/gr-account-info_test.html
+++ b/polygerrit-ui/app/elements/settings/gr-account-info/gr-account-info_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-account-info</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>
diff --git a/polygerrit-ui/app/elements/settings/gr-agreements-list/gr-agreements-list.js b/polygerrit-ui/app/elements/settings/gr-agreements-list/gr-agreements-list.js
index 41595a98..13992f8 100644
--- a/polygerrit-ui/app/elements/settings/gr-agreements-list/gr-agreements-list.js
+++ b/polygerrit-ui/app/elements/settings/gr-agreements-list/gr-agreements-list.js
@@ -17,33 +17,41 @@
 (function() {
   'use strict';
 
-  Polymer({
-    is: 'gr-agreements-list',
+  /**
+    * @appliesMixin Gerrit.BaseUrlMixin
+    */
+  class GrAgreementsList extends Polymer.mixinBehaviors( [
+    Gerrit.BaseUrlBehavior,
+  ], Polymer.GestureEventListeners(
+      Polymer.LegacyElementMixin(
+          Polymer.Element))) {
+    static get is() { return 'gr-agreements-list'; }
 
-    properties: {
-      _agreements: Array,
-    },
-
-    behaviors: [
-      Gerrit.BaseUrlBehavior,
-    ],
+    static get properties() {
+      return {
+        _agreements: Array,
+      };
+    }
 
     attached() {
+      super.attached();
       this.loadData();
-    },
+    }
 
     loadData() {
       return this.$.restAPI.getAccountAgreements().then(agreements => {
         this._agreements = agreements;
       });
-    },
+    }
 
     getUrl() {
       return this.getBaseUrl() + '/settings/new-agreement';
-    },
+    }
 
     getUrlBase(item) {
       return this.getBaseUrl() + '/' + item;
-    },
-  });
+    }
+  }
+
+  customElements.define(GrAgreementsList.is, GrAgreementsList);
 })();
diff --git a/polygerrit-ui/app/elements/settings/gr-agreements-list/gr-agreements-list_test.html b/polygerrit-ui/app/elements/settings/gr-agreements-list/gr-agreements-list_test.html
index 14cf97c..cd5bc09 100644
--- a/polygerrit-ui/app/elements/settings/gr-agreements-list/gr-agreements-list_test.html
+++ b/polygerrit-ui/app/elements/settings/gr-agreements-list/gr-agreements-list_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-settings-view</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>
diff --git a/polygerrit-ui/app/elements/settings/gr-change-table-editor/gr-change-table-editor.js b/polygerrit-ui/app/elements/settings/gr-change-table-editor/gr-change-table-editor.js
index 85eed9c..d92c148 100644
--- a/polygerrit-ui/app/elements/settings/gr-change-table-editor/gr-change-table-editor.js
+++ b/polygerrit-ui/app/elements/settings/gr-change-table-editor/gr-change-table-editor.js
@@ -17,23 +17,28 @@
 (function() {
   'use strict';
 
-  Polymer({
-    is: 'gr-change-table-editor',
+  /**
+    * @appliesMixin Gerrit.ChangeTableMixin
+    */
+  class GrChangeTableEditor extends Polymer.mixinBehaviors( [
+    Gerrit.ChangeTableBehavior,
+  ], Polymer.GestureEventListeners(
+      Polymer.LegacyElementMixin(
+          Polymer.Element))) {
+    static get is() { return 'gr-change-table-editor'; }
 
-    properties: {
-      displayedColumns: {
-        type: Array,
-        notify: true,
-      },
-      showNumber: {
-        type: Boolean,
-        notify: true,
-      },
-    },
-
-    behaviors: [
-      Gerrit.ChangeTableBehavior,
-    ],
+    static get properties() {
+      return {
+        displayedColumns: {
+          type: Array,
+          notify: true,
+        },
+        showNumber: {
+          type: Boolean,
+          notify: true,
+        },
+      };
+    }
 
     /**
      * Get the list of enabled column names from whichever checkboxes are
@@ -45,7 +50,7 @@
           .querySelectorAll('.checkboxContainer input:not([name=number])'))
           .filter(checkbox => checkbox.checked)
           .map(checkbox => checkbox.name);
-    },
+    }
 
     /**
      * Handle a click on a checkbox container and relay the click to the checkbox it
@@ -55,7 +60,7 @@
       const checkbox = Polymer.dom(e.target).querySelector('input');
       if (!checkbox) { return; }
       checkbox.click();
-    },
+    }
 
     /**
      * Handle a click on the number checkbox and update the showNumber property
@@ -63,7 +68,7 @@
      */
     _handleNumberCheckboxClick(e) {
       this.showNumber = Polymer.dom(e).rootTarget.checked;
-    },
+    }
 
     /**
      * Handle a click on a displayed column checkboxes (excluding number) and
@@ -71,6 +76,8 @@
      */
     _handleTargetClick(e) {
       this.set('displayedColumns', this._getDisplayedColumns());
-    },
-  });
+    }
+  }
+
+  customElements.define(GrChangeTableEditor.is, GrChangeTableEditor);
 })();
diff --git a/polygerrit-ui/app/elements/settings/gr-change-table-editor/gr-change-table-editor_test.html b/polygerrit-ui/app/elements/settings/gr-change-table-editor/gr-change-table-editor_test.html
index 29a7081..ec37c03 100644
--- a/polygerrit-ui/app/elements/settings/gr-change-table-editor/gr-change-table-editor_test.html
+++ b/polygerrit-ui/app/elements/settings/gr-change-table-editor/gr-change-table-editor_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-settings-view</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>
diff --git a/polygerrit-ui/app/elements/settings/gr-cla-view/gr-cla-view.js b/polygerrit-ui/app/elements/settings/gr-cla-view/gr-cla-view.js
index 41a9800..98f1413 100644
--- a/polygerrit-ui/app/elements/settings/gr-cla-view/gr-cla-view.js
+++ b/polygerrit-ui/app/elements/settings/gr-cla-view/gr-cla-view.js
@@ -17,33 +17,40 @@
 (function() {
   'use strict';
 
-  Polymer({
-    is: 'gr-cla-view',
+  /**
+    * @appliesMixin Gerrit.BaseUrlMixin
+    * @appliesMixin Gerrit.FireMixin
+    */
+  class GrClaView extends Polymer.mixinBehaviors( [
+    Gerrit.BaseUrlBehavior,
+    Gerrit.FireBehavior,
+  ], Polymer.GestureEventListeners(
+      Polymer.LegacyElementMixin(
+          Polymer.Element))) {
+    static get is() { return 'gr-cla-view'; }
 
-    properties: {
-      _groups: Object,
-      /** @type {?} */
-      _serverConfig: Object,
-      _agreementsText: String,
-      _agreementName: String,
-      _signedAgreements: Array,
-      _showAgreements: {
-        type: Boolean,
-        value: false,
-      },
-      _agreementsUrl: String,
-    },
-
-    behaviors: [
-      Gerrit.BaseUrlBehavior,
-      Gerrit.FireBehavior,
-    ],
+    static get properties() {
+      return {
+        _groups: Object,
+        /** @type {?} */
+        _serverConfig: Object,
+        _agreementsText: String,
+        _agreementName: String,
+        _signedAgreements: Array,
+        _showAgreements: {
+          type: Boolean,
+          value: false,
+        },
+        _agreementsUrl: String,
+      };
+    }
 
     attached() {
+      super.attached();
       this.loadData();
 
       this.fire('title-change', {title: 'New Contributor Agreement'});
-    },
+    }
 
     loadData() {
       const promises = [];
@@ -62,7 +69,7 @@
       }));
 
       return Promise.all(promises);
-    },
+    }
 
     _getAgreementsUrl(configUrl) {
       let url;
@@ -76,14 +83,14 @@
       }
 
       return url;
-    },
+    }
 
     _handleShowAgreement(e) {
       this._agreementName = e.target.getAttribute('data-name');
       this._agreementsUrl =
           this._getAgreementsUrl(e.target.getAttribute('data-url'));
       this._showAgreements = true;
-    },
+    }
 
     _handleSaveAgreements(e) {
       this._createToast('Agreement saving...');
@@ -99,16 +106,16 @@
         this._agreementsText = '';
         this._showAgreements = false;
       });
-    },
+    }
 
     _createToast(message) {
       this.dispatchEvent(new CustomEvent(
           'show-alert', {detail: {message}, bubbles: true, composed: true}));
-    },
+    }
 
     _computeShowAgreementsClass(agreements) {
       return agreements ? 'show' : '';
-    },
+    }
 
     _disableAggreements(item, groups, signedAgreements) {
       for (const group of groups) {
@@ -119,16 +126,16 @@
         }
       }
       return false;
-    },
+    }
 
     _hideAggreements(item, groups, signedAgreements) {
       return this._disableAggreements(item, groups, signedAgreements) ?
         '' : 'hide';
-    },
+    }
 
     _disableAgreementsText(text) {
       return text.toLowerCase() === 'i agree' ? false : true;
-    },
+    }
 
     // This checks for auto_verify_group,
     // if specified it returns 'hideAgreementsTextBox' which
@@ -148,6 +155,8 @@
           }
         }
       }
-    },
-  });
+    }
+  }
+
+  customElements.define(GrClaView.is, GrClaView);
 })();
diff --git a/polygerrit-ui/app/elements/settings/gr-cla-view/gr-cla-view_test.html b/polygerrit-ui/app/elements/settings/gr-cla-view/gr-cla-view_test.html
index 53d6be1..833fa39 100644
--- a/polygerrit-ui/app/elements/settings/gr-cla-view/gr-cla-view_test.html
+++ b/polygerrit-ui/app/elements/settings/gr-cla-view/gr-cla-view_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-cla-view</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>
diff --git a/polygerrit-ui/app/elements/settings/gr-edit-preferences/gr-edit-preferences.js b/polygerrit-ui/app/elements/settings/gr-edit-preferences/gr-edit-preferences.js
index 86350f9..36e53b0 100644
--- a/polygerrit-ui/app/elements/settings/gr-edit-preferences/gr-edit-preferences.js
+++ b/polygerrit-ui/app/elements/settings/gr-edit-preferences/gr-edit-preferences.js
@@ -17,66 +17,72 @@
 (function() {
   'use strict';
 
-  Polymer({
-    is: 'gr-edit-preferences',
+  class GrEditPreferences extends Polymer.GestureEventListeners(
+      Polymer.LegacyElementMixin(
+          Polymer.Element)) {
+    static get is() { return 'gr-edit-preferences'; }
 
-    properties: {
-      hasUnsavedChanges: {
-        type: Boolean,
-        notify: true,
-        value: false,
-      },
+    static get properties() {
+      return {
+        hasUnsavedChanges: {
+          type: Boolean,
+          notify: true,
+          value: false,
+        },
 
-      /** @type {?} */
-      editPrefs: Object,
-    },
+        /** @type {?} */
+        editPrefs: Object,
+      };
+    }
 
     loadData() {
       return this.$.restAPI.getEditPreferences().then(prefs => {
         this.editPrefs = prefs;
       });
-    },
+    }
 
     _handleEditPrefsChanged() {
       this.hasUnsavedChanges = true;
-    },
+    }
 
     _handleEditSyntaxHighlightingChanged() {
       this.set('editPrefs.syntax_highlighting',
           this.$.editSyntaxHighlighting.checked);
       this._handleEditPrefsChanged();
-    },
+    }
 
     _handleEditShowTabsChanged() {
       this.set('editPrefs.show_tabs', this.$.editShowTabs.checked);
       this._handleEditPrefsChanged();
-    },
+    }
 
     _handleMatchBracketsChanged() {
       this.set('editPrefs.match_brackets', this.$.showMatchBrackets.checked);
       this._handleEditPrefsChanged();
-    },
+    }
 
     _handleEditLineWrappingChanged() {
       this.set('editPrefs.line_wrapping', this.$.editShowLineWrapping.checked);
       this._handleEditPrefsChanged();
-    },
+    }
 
     _handleIndentWithTabsChanged() {
       this.set('editPrefs.indent_with_tabs', this.$.showIndentWithTabs.checked);
       this._handleEditPrefsChanged();
-    },
+    }
 
     _handleAutoCloseBracketsChanged() {
       this.set('editPrefs.auto_close_brackets',
           this.$.showAutoCloseBrackets.checked);
       this._handleEditPrefsChanged();
-    },
+    }
 
     save() {
       return this.$.restAPI.saveEditPreferences(this.editPrefs).then(res => {
         this.hasUnsavedChanges = false;
       });
-    },
-  });
+    }
+  }
+
+  customElements.define(GrEditPreferences.is, GrEditPreferences);
 })();
diff --git a/polygerrit-ui/app/elements/settings/gr-edit-preferences/gr-edit-preferences_test.html b/polygerrit-ui/app/elements/settings/gr-edit-preferences/gr-edit-preferences_test.html
index c1c5c52..8b0bf86 100644
--- a/polygerrit-ui/app/elements/settings/gr-edit-preferences/gr-edit-preferences_test.html
+++ b/polygerrit-ui/app/elements/settings/gr-edit-preferences/gr-edit-preferences_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-edit-preferences</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>
diff --git a/polygerrit-ui/app/elements/settings/gr-email-editor/gr-email-editor.js b/polygerrit-ui/app/elements/settings/gr-email-editor/gr-email-editor.js
index 8490b26..8ec1067 100644
--- a/polygerrit-ui/app/elements/settings/gr-email-editor/gr-email-editor.js
+++ b/polygerrit-ui/app/elements/settings/gr-email-editor/gr-email-editor.js
@@ -17,33 +17,37 @@
 (function() {
   'use strict';
 
-  Polymer({
-    is: 'gr-email-editor',
+  class GrEmailEditor extends Polymer.GestureEventListeners(
+      Polymer.LegacyElementMixin(
+          Polymer.Element)) {
+    static get is() { return 'gr-email-editor'; }
 
-    properties: {
-      hasUnsavedChanges: {
-        type: Boolean,
-        notify: true,
-        value: false,
-      },
+    static get properties() {
+      return {
+        hasUnsavedChanges: {
+          type: Boolean,
+          notify: true,
+          value: false,
+        },
 
-      _emails: Array,
-      _emailsToRemove: {
-        type: Array,
-        value() { return []; },
-      },
-      /** @type {?string} */
-      _newPreferred: {
-        type: String,
-        value: null,
-      },
-    },
+        _emails: Array,
+        _emailsToRemove: {
+          type: Array,
+          value() { return []; },
+        },
+        /** @type {?string} */
+        _newPreferred: {
+          type: String,
+          value: null,
+        },
+      };
+    }
 
     loadData() {
       return this.$.restAPI.getAccountEmails().then(emails => {
         this._emails = emails;
       });
-    },
+    }
 
     save() {
       const promises = [];
@@ -62,7 +66,7 @@
         this._newPreferred = null;
         this.hasUnsavedChanges = false;
       });
-    },
+    }
 
     _handleDeleteButton(e) {
       const index = parseInt(Polymer.dom(e).localTarget
@@ -71,13 +75,13 @@
       this.push('_emailsToRemove', email);
       this.splice('_emails', index, 1);
       this.hasUnsavedChanges = true;
-    },
+    }
 
     _handlePreferredControlClick(e) {
       if (e.target.classList.contains('preferredControl')) {
         e.target.firstElementChild.click();
       }
-    },
+    }
 
     _handlePreferredChange(e) {
       const preferred = e.target.value;
@@ -90,6 +94,8 @@
           this.set(['_emails', i, 'preferred'], false);
         }
       }
-    },
-  });
+    }
+  }
+
+  customElements.define(GrEmailEditor.is, GrEmailEditor);
 })();
diff --git a/polygerrit-ui/app/elements/settings/gr-email-editor/gr-email-editor_test.html b/polygerrit-ui/app/elements/settings/gr-email-editor/gr-email-editor_test.html
index 8d3f2d2..e55ac97 100644
--- a/polygerrit-ui/app/elements/settings/gr-email-editor/gr-email-editor_test.html
+++ b/polygerrit-ui/app/elements/settings/gr-email-editor/gr-email-editor_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-email-editor</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>
diff --git a/polygerrit-ui/app/elements/settings/gr-gpg-editor/gr-gpg-editor.js b/polygerrit-ui/app/elements/settings/gr-gpg-editor/gr-gpg-editor.js
index 890061e..14d7a4c 100644
--- a/polygerrit-ui/app/elements/settings/gr-gpg-editor/gr-gpg-editor.js
+++ b/polygerrit-ui/app/elements/settings/gr-gpg-editor/gr-gpg-editor.js
@@ -17,27 +17,31 @@
 (function() {
   'use strict';
 
-  Polymer({
-    is: 'gr-gpg-editor',
+  class GrGpgEditor extends Polymer.GestureEventListeners(
+      Polymer.LegacyElementMixin(
+          Polymer.Element)) {
+    static get is() { return 'gr-gpg-editor'; }
 
-    properties: {
-      hasUnsavedChanges: {
-        type: Boolean,
-        value: false,
-        notify: true,
-      },
-      _keys: Array,
-      /** @type {?} */
-      _keyToView: Object,
-      _newKey: {
-        type: String,
-        value: '',
-      },
-      _keysToRemove: {
-        type: Array,
-        value() { return []; },
-      },
-    },
+    static get properties() {
+      return {
+        hasUnsavedChanges: {
+          type: Boolean,
+          value: false,
+          notify: true,
+        },
+        _keys: Array,
+        /** @type {?} */
+        _keyToView: Object,
+        _newKey: {
+          type: String,
+          value: '',
+        },
+        _keysToRemove: {
+          type: Array,
+          value() { return []; },
+        },
+      };
+    }
 
     loadData() {
       this._keys = [];
@@ -52,7 +56,7 @@
               return gpgKey;
             });
       });
-    },
+    }
 
     save() {
       const promises = this._keysToRemove.map(key => {
@@ -63,18 +67,18 @@
         this._keysToRemove = [];
         this.hasUnsavedChanges = false;
       });
-    },
+    }
 
     _showKey(e) {
       const el = Polymer.dom(e).localTarget;
       const index = parseInt(el.getAttribute('data-index'), 10);
       this._keyToView = this._keys[index];
       this.$.viewKeyOverlay.open();
-    },
+    }
 
     _closeOverlay() {
       this.$.viewKeyOverlay.close();
-    },
+    }
 
     _handleDeleteKey(e) {
       const el = Polymer.dom(e).localTarget;
@@ -82,7 +86,7 @@
       this.push('_keysToRemove', this._keys[index]);
       this.splice('_keys', index, 1);
       this.hasUnsavedChanges = true;
-    },
+    }
 
     _handleAddKey() {
       this.$.addButton.disabled = true;
@@ -96,10 +100,12 @@
             this.$.addButton.disabled = false;
             this.$.newKey.disabled = false;
           });
-    },
+    }
 
     _computeAddButtonDisabled(newKey) {
       return !newKey.length;
-    },
-  });
+    }
+  }
+
+  customElements.define(GrGpgEditor.is, GrGpgEditor);
 })();
diff --git a/polygerrit-ui/app/elements/settings/gr-gpg-editor/gr-gpg-editor_test.html b/polygerrit-ui/app/elements/settings/gr-gpg-editor/gr-gpg-editor_test.html
index 9cfbde5f..575285b 100644
--- a/polygerrit-ui/app/elements/settings/gr-gpg-editor/gr-gpg-editor_test.html
+++ b/polygerrit-ui/app/elements/settings/gr-gpg-editor/gr-gpg-editor_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-gpg-editor</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>
diff --git a/polygerrit-ui/app/elements/settings/gr-group-list/gr-group-list.js b/polygerrit-ui/app/elements/settings/gr-group-list/gr-group-list.js
index d62a241..87fb852 100644
--- a/polygerrit-ui/app/elements/settings/gr-group-list/gr-group-list.js
+++ b/polygerrit-ui/app/elements/settings/gr-group-list/gr-group-list.js
@@ -17,12 +17,16 @@
 (function() {
   'use strict';
 
-  Polymer({
-    is: 'gr-group-list',
+  class GrGroupList extends Polymer.GestureEventListeners(
+      Polymer.LegacyElementMixin(
+          Polymer.Element)) {
+    static get is() { return 'gr-group-list'; }
 
-    properties: {
-      _groups: Array,
-    },
+    static get properties() {
+      return {
+        _groups: Array,
+      };
+    }
 
     loadData() {
       return this.$.restAPI.getAccountGroups().then(groups => {
@@ -30,11 +34,11 @@
           return a.name.localeCompare(b.name);
         });
       });
-    },
+    }
 
     _computeVisibleToAll(group) {
       return group.options.visible_to_all ? 'Yes' : 'No';
-    },
+    }
 
     _computeGroupPath(group) {
       if (!group || !group.id) { return; }
@@ -42,6 +46,8 @@
       // Group ID is already encoded from the API
       // Decode it here to match with our router encoding behavior
       return Gerrit.Nav.getUrlForGroup(decodeURIComponent(group.id));
-    },
-  });
+    }
+  }
+
+  customElements.define(GrGroupList.is, GrGroupList);
 })();
diff --git a/polygerrit-ui/app/elements/settings/gr-group-list/gr-group-list_test.html b/polygerrit-ui/app/elements/settings/gr-group-list/gr-group-list_test.html
index 3c3ece36..d8bf888 100644
--- a/polygerrit-ui/app/elements/settings/gr-group-list/gr-group-list_test.html
+++ b/polygerrit-ui/app/elements/settings/gr-group-list/gr-group-list_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-settings-view</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>
diff --git a/polygerrit-ui/app/elements/settings/gr-http-password/gr-http-password.js b/polygerrit-ui/app/elements/settings/gr-http-password/gr-http-password.js
index 003e471..5c0a059 100644
--- a/polygerrit-ui/app/elements/settings/gr-http-password/gr-http-password.js
+++ b/polygerrit-ui/app/elements/settings/gr-http-password/gr-http-password.js
@@ -17,18 +17,23 @@
 (function() {
   'use strict';
 
-  Polymer({
-    is: 'gr-http-password',
+  class GrHttpPassword extends Polymer.GestureEventListeners(
+      Polymer.LegacyElementMixin(
+          Polymer.Element)) {
+    static get is() { return 'gr-http-password'; }
 
-    properties: {
-      _username: String,
-      _generatedPassword: String,
-      _passwordUrl: String,
-    },
+    static get properties() {
+      return {
+        _username: String,
+        _generatedPassword: String,
+        _passwordUrl: String,
+      };
+    }
 
     attached() {
+      super.attached();
       this.loadData();
-    },
+    }
 
     loadData() {
       const promises = [];
@@ -42,7 +47,7 @@
       }));
 
       return Promise.all(promises);
-    },
+    }
 
     _handleGenerateTap() {
       this._generatedPassword = 'Generating...';
@@ -50,14 +55,16 @@
       this.$.restAPI.generateAccountHttpPassword().then(newPassword => {
         this._generatedPassword = newPassword;
       });
-    },
+    }
 
     _closeOverlay() {
       this.$.generatedPasswordOverlay.close();
-    },
+    }
 
     _generatedPasswordOverlayClosed() {
       this._generatedPassword = '';
-    },
-  });
+    }
+  }
+
+  customElements.define(GrHttpPassword.is, GrHttpPassword);
 })();
diff --git a/polygerrit-ui/app/elements/settings/gr-http-password/gr-http-password_test.html b/polygerrit-ui/app/elements/settings/gr-http-password/gr-http-password_test.html
index 8924058..89da766 100644
--- a/polygerrit-ui/app/elements/settings/gr-http-password/gr-http-password_test.html
+++ b/polygerrit-ui/app/elements/settings/gr-http-password/gr-http-password_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-settings-view</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>
diff --git a/polygerrit-ui/app/elements/settings/gr-identities/gr-identities.js b/polygerrit-ui/app/elements/settings/gr-identities/gr-identities.js
index c927f1e..94c9e5a 100644
--- a/polygerrit-ui/app/elements/settings/gr-identities/gr-identities.js
+++ b/polygerrit-ui/app/elements/settings/gr-identities/gr-identities.js
@@ -22,61 +22,66 @@
     'OAUTH',
   ];
 
-  Polymer({
-    is: 'gr-identities',
+  /**
+    * @appliesMixin Gerrit.BaseUrlMixin
+    */
+  class GrIdentities extends Polymer.mixinBehaviors( [
+    Gerrit.BaseUrlBehavior,
+  ], Polymer.GestureEventListeners(
+      Polymer.LegacyElementMixin(
+          Polymer.Element))) {
+    static get is() { return 'gr-identities'; }
 
-    properties: {
-      _identities: Object,
-      _idName: String,
-      serverConfig: Object,
-      _showLinkAnotherIdentity: {
-        type: Boolean,
-        computed: '_computeShowLinkAnotherIdentity(serverConfig)',
-      },
-    },
-
-    behaviors: [
-      Gerrit.BaseUrlBehavior,
-    ],
+    static get properties() {
+      return {
+        _identities: Object,
+        _idName: String,
+        serverConfig: Object,
+        _showLinkAnotherIdentity: {
+          type: Boolean,
+          computed: '_computeShowLinkAnotherIdentity(serverConfig)',
+        },
+      };
+    }
 
     loadData() {
       return this.$.restAPI.getExternalIds().then(id => {
         this._identities = id;
       });
-    },
+    }
 
     _computeIdentity(id) {
       return id && id.startsWith('mailto:') ? '' : id;
-    },
+    }
 
     _computeHideDeleteClass(canDelete) {
       return canDelete ? 'show' : '';
-    },
+    }
 
     _handleDeleteItemConfirm() {
       this.$.overlay.close();
       return this.$.restAPI.deleteAccountIdentity([this._idName])
           .then(() => { this.loadData(); });
-    },
+    }
 
     _handleConfirmDialogCancel() {
       this.$.overlay.close();
-    },
+    }
 
     _handleDeleteItem(e) {
       const name = e.model.get('item.identity');
       if (!name) { return; }
       this._idName = name;
       this.$.overlay.open();
-    },
+    }
 
     _computeIsTrusted(item) {
       return item ? '' : 'Untrusted';
-    },
+    }
 
     filterIdentities(item) {
       return !item.identity.startsWith('username:');
-    },
+    }
 
     _computeShowLinkAnotherIdentity(config) {
       if (config && config.auth &&
@@ -86,7 +91,7 @@
       }
 
       return false;
-    },
+    }
 
     _computeLinkAnotherIdentity() {
       const baseUrl = this.getBaseUrl() || '';
@@ -95,6 +100,8 @@
         pathname = '/' + pathname.substring(baseUrl.length);
       }
       return baseUrl + '/login/' + encodeURIComponent(pathname) + '?link';
-    },
-  });
+    }
+  }
+
+  customElements.define(GrIdentities.is, GrIdentities);
 })();
diff --git a/polygerrit-ui/app/elements/settings/gr-identities/gr-identities_test.html b/polygerrit-ui/app/elements/settings/gr-identities/gr-identities_test.html
index 1277424..e2f8cad 100644
--- a/polygerrit-ui/app/elements/settings/gr-identities/gr-identities_test.html
+++ b/polygerrit-ui/app/elements/settings/gr-identities/gr-identities_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-identities</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>
diff --git a/polygerrit-ui/app/elements/settings/gr-menu-editor/gr-menu-editor.js b/polygerrit-ui/app/elements/settings/gr-menu-editor/gr-menu-editor.js
index 4f3c0c7..5b6c978 100644
--- a/polygerrit-ui/app/elements/settings/gr-menu-editor/gr-menu-editor.js
+++ b/polygerrit-ui/app/elements/settings/gr-menu-editor/gr-menu-editor.js
@@ -17,14 +17,18 @@
 (function() {
   'use strict';
 
-  Polymer({
-    is: 'gr-menu-editor',
+  class GrMenuEditor extends Polymer.GestureEventListeners(
+      Polymer.LegacyElementMixin(
+          Polymer.Element)) {
+    static get is() { return 'gr-menu-editor'; }
 
-    properties: {
-      menuItems: Array,
-      _newName: String,
-      _newUrl: String,
-    },
+    static get properties() {
+      return {
+        menuItems: Array,
+        _newName: String,
+        _newUrl: String,
+      };
+    }
 
     _handleMoveUpButton(e) {
       const index = Number(Polymer.dom(e).localTarget.dataset.index);
@@ -32,7 +36,7 @@
       const row = this.menuItems[index];
       const prev = this.menuItems[index - 1];
       this.splice('menuItems', index - 1, 2, row, prev);
-    },
+    }
 
     _handleMoveDownButton(e) {
       const index = Number(Polymer.dom(e).localTarget.dataset.index);
@@ -40,12 +44,12 @@
       const row = this.menuItems[index];
       const next = this.menuItems[index + 1];
       this.splice('menuItems', index, 2, next, row);
-    },
+    }
 
     _handleDeleteButton(e) {
       const index = Number(Polymer.dom(e).localTarget.dataset.index);
       this.splice('menuItems', index, 1);
-    },
+    }
 
     _handleAddButton() {
       if (this._computeAddDisabled(this._newName, this._newUrl)) { return; }
@@ -58,17 +62,19 @@
 
       this._newName = '';
       this._newUrl = '';
-    },
+    }
 
     _computeAddDisabled(newName, newUrl) {
       return !newName.length || !newUrl.length;
-    },
+    }
 
     _handleInputKeydown(e) {
       if (e.keyCode === 13) {
         e.stopPropagation();
         this._handleAddButton();
       }
-    },
-  });
+    }
+  }
+
+  customElements.define(GrMenuEditor.is, GrMenuEditor);
 })();
diff --git a/polygerrit-ui/app/elements/settings/gr-menu-editor/gr-menu-editor_test.html b/polygerrit-ui/app/elements/settings/gr-menu-editor/gr-menu-editor_test.html
index 134e018..380e00a 100644
--- a/polygerrit-ui/app/elements/settings/gr-menu-editor/gr-menu-editor_test.html
+++ b/polygerrit-ui/app/elements/settings/gr-menu-editor/gr-menu-editor_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-settings-view</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>
diff --git a/polygerrit-ui/app/elements/settings/gr-registration-dialog/gr-registration-dialog.js b/polygerrit-ui/app/elements/settings/gr-registration-dialog/gr-registration-dialog.js
index 0633416..af23f5b 100644
--- a/polygerrit-ui/app/elements/settings/gr-registration-dialog/gr-registration-dialog.js
+++ b/polygerrit-ui/app/elements/settings/gr-registration-dialog/gr-registration-dialog.js
@@ -17,9 +17,15 @@
 (function() {
   'use strict';
 
-  Polymer({
-    is: 'gr-registration-dialog',
-
+  /**
+    * @appliesMixin Gerrit.FireMixin
+    */
+  class GrRegistrationDialog extends Polymer.mixinBehaviors( [
+    Gerrit.FireBehavior,
+  ], Polymer.GestureEventListeners(
+      Polymer.LegacyElementMixin(
+          Polymer.Element))) {
+    static get is() { return 'gr-registration-dialog'; }
     /**
      * Fired when account details are changed.
      *
@@ -32,40 +38,39 @@
      * @event close
      */
 
-    properties: {
-      settingsUrl: String,
-      /** @type {?} */
-      _account: {
-        type: Object,
-        value: () => {
+    static get properties() {
+      return {
+        settingsUrl: String,
+        /** @type {?} */
+        _account: {
+          type: Object,
+          value: () => {
           // Prepopulate possibly undefined fields with values to trigger
           // computed bindings.
-          return {email: null, name: null, username: null};
+            return {email: null, name: null, username: null};
+          },
         },
-      },
-      _usernameMutable: {
-        type: Boolean,
-        computed: '_computeUsernameMutable(_serverConfig, _account.username)',
-      },
-      _loading: {
-        type: Boolean,
-        value: true,
-        observer: '_loadingChanged',
-      },
-      _saving: {
-        type: Boolean,
-        value: false,
-      },
-      _serverConfig: Object,
-    },
+        _usernameMutable: {
+          type: Boolean,
+          computed: '_computeUsernameMutable(_serverConfig, _account.username)',
+        },
+        _loading: {
+          type: Boolean,
+          value: true,
+          observer: '_loadingChanged',
+        },
+        _saving: {
+          type: Boolean,
+          value: false,
+        },
+        _serverConfig: Object,
+      };
+    }
 
-    behaviors: [
-      Gerrit.FireBehavior,
-    ],
-
-    hostAttributes: {
-      role: 'dialog',
-    },
+    ready() {
+      super.ready();
+      this._ensureAttribute('role', 'dialog');
+    }
 
     loadData() {
       this._loading = true;
@@ -84,7 +89,7 @@
       return Promise.all([loadAccount, loadConfig]).then(() => {
         this._loading = false;
       });
-    },
+    }
 
     _save() {
       this._saving = true;
@@ -101,26 +106,26 @@
         this._saving = false;
         this.fire('account-detail-update');
       });
-    },
+    }
 
     _handleSave(e) {
       e.preventDefault();
       this._save().then(this.close.bind(this));
-    },
+    }
 
     _handleClose(e) {
       e.preventDefault();
       this.close();
-    },
+    }
 
     close() {
       this._saving = true; // disable buttons indefinitely
       this.fire('close');
-    },
+    }
 
     _computeSaveDisabled(name, email, saving) {
       return !name || !email || saving;
-    },
+    }
 
     _computeUsernameMutable(config, username) {
       // Polymer 2: check for undefined
@@ -133,14 +138,16 @@
 
       return config.auth.editable_account_fields.includes('USER_NAME') &&
           !username;
-    },
+    }
 
     _computeUsernameClass(usernameMutable) {
       return usernameMutable ? '' : 'hide';
-    },
+    }
 
     _loadingChanged() {
       this.classList.toggle('loading', this._loading);
-    },
-  });
+    }
+  }
+
+  customElements.define(GrRegistrationDialog.is, GrRegistrationDialog);
 })();
diff --git a/polygerrit-ui/app/elements/settings/gr-registration-dialog/gr-registration-dialog_test.html b/polygerrit-ui/app/elements/settings/gr-registration-dialog/gr-registration-dialog_test.html
index d1b5c80..a987a26 100644
--- a/polygerrit-ui/app/elements/settings/gr-registration-dialog/gr-registration-dialog_test.html
+++ b/polygerrit-ui/app/elements/settings/gr-registration-dialog/gr-registration-dialog_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-registration-dialog</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>
diff --git a/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-item.js b/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-item.js
index dae3b68..9702280 100644
--- a/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-item.js
+++ b/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-item.js
@@ -17,12 +17,18 @@
 (function() {
   'use strict';
 
-  Polymer({
-    is: 'gr-settings-item',
+  class GrSettingsItem extends Polymer.GestureEventListeners(
+      Polymer.LegacyElementMixin(
+          Polymer.Element)) {
+    static get is() { return 'gr-settings-item'; }
 
-    properties: {
-      anchor: String,
-      title: String,
-    },
-  });
+    static get properties() {
+      return {
+        anchor: String,
+        title: String,
+      };
+    }
+  }
+
+  customElements.define(GrSettingsItem.is, GrSettingsItem);
 })();
diff --git a/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-menu-item.js b/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-menu-item.js
index 5db0031..e08e3d8 100644
--- a/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-menu-item.js
+++ b/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-menu-item.js
@@ -17,12 +17,18 @@
 (function() {
   'use strict';
 
-  Polymer({
-    is: 'gr-settings-menu-item',
+  class GrSettingsMenuItem extends Polymer.GestureEventListeners(
+      Polymer.LegacyElementMixin(
+          Polymer.Element)) {
+    static get is() { return 'gr-settings-menu-item'; }
 
-    properties: {
-      href: String,
-      title: String,
-    },
-  });
+    static get properties() {
+      return {
+        href: String,
+        title: String,
+      };
+    }
+  }
+
+  customElements.define(GrSettingsMenuItem.is, GrSettingsMenuItem);
 })();
diff --git a/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-view.js b/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-view.js
index 644b503..39ca220 100644
--- a/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-view.js
+++ b/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-view.js
@@ -45,9 +45,19 @@
     'HTTP_LDAP',
   ];
 
-  Polymer({
-    is: 'gr-settings-view',
-
+  /**
+    * @appliesMixin Gerrit.DocsUrlMixin
+    * @appliesMixin Gerrit.ChangeTableMixin
+    * @appliesMixin Gerrit.FireMixin
+    */
+  class GrSettingsView extends Polymer.mixinBehaviors( [
+    Gerrit.DocsUrlBehavior,
+    Gerrit.ChangeTableBehavior,
+    Gerrit.FireBehavior,
+  ], Polymer.GestureEventListeners(
+      Polymer.LegacyElementMixin(
+          Polymer.Element))) {
+    static get is() { return 'gr-settings-view'; }
     /**
      * Fired when the title of the page should change.
      *
@@ -60,103 +70,102 @@
      * @event show-alert
      */
 
-    properties: {
-      prefs: {
-        type: Object,
-        value() { return {}; },
-      },
-      params: {
-        type: Object,
-        value() { return {}; },
-      },
-      _accountInfoChanged: Boolean,
-      _changeTableColumnsNotDisplayed: Array,
-      /** @type {?} */
-      _localPrefs: {
-        type: Object,
-        value() { return {}; },
-      },
-      _localChangeTableColumns: {
-        type: Array,
-        value() { return []; },
-      },
-      _localMenu: {
-        type: Array,
-        value() { return []; },
-      },
-      _loading: {
-        type: Boolean,
-        value: true,
-      },
-      _changeTableChanged: {
-        type: Boolean,
-        value: false,
-      },
-      _prefsChanged: {
-        type: Boolean,
-        value: false,
-      },
-      /** @type {?} */
-      _diffPrefsChanged: Boolean,
-      /** @type {?} */
-      _editPrefsChanged: Boolean,
-      _menuChanged: {
-        type: Boolean,
-        value: false,
-      },
-      _watchedProjectsChanged: {
-        type: Boolean,
-        value: false,
-      },
-      _keysChanged: {
-        type: Boolean,
-        value: false,
-      },
-      _gpgKeysChanged: {
-        type: Boolean,
-        value: false,
-      },
-      _newEmail: String,
-      _addingEmail: {
-        type: Boolean,
-        value: false,
-      },
-      _lastSentVerificationEmail: {
-        type: String,
-        value: null,
-      },
-      /** @type {?} */
-      _serverConfig: Object,
-      /** @type {?string} */
-      _docsBaseUrl: String,
-      _emailsChanged: Boolean,
+    static get properties() {
+      return {
+        prefs: {
+          type: Object,
+          value() { return {}; },
+        },
+        params: {
+          type: Object,
+          value() { return {}; },
+        },
+        _accountInfoChanged: Boolean,
+        _changeTableColumnsNotDisplayed: Array,
+        /** @type {?} */
+        _localPrefs: {
+          type: Object,
+          value() { return {}; },
+        },
+        _localChangeTableColumns: {
+          type: Array,
+          value() { return []; },
+        },
+        _localMenu: {
+          type: Array,
+          value() { return []; },
+        },
+        _loading: {
+          type: Boolean,
+          value: true,
+        },
+        _changeTableChanged: {
+          type: Boolean,
+          value: false,
+        },
+        _prefsChanged: {
+          type: Boolean,
+          value: false,
+        },
+        /** @type {?} */
+        _diffPrefsChanged: Boolean,
+        /** @type {?} */
+        _editPrefsChanged: Boolean,
+        _menuChanged: {
+          type: Boolean,
+          value: false,
+        },
+        _watchedProjectsChanged: {
+          type: Boolean,
+          value: false,
+        },
+        _keysChanged: {
+          type: Boolean,
+          value: false,
+        },
+        _gpgKeysChanged: {
+          type: Boolean,
+          value: false,
+        },
+        _newEmail: String,
+        _addingEmail: {
+          type: Boolean,
+          value: false,
+        },
+        _lastSentVerificationEmail: {
+          type: String,
+          value: null,
+        },
+        /** @type {?} */
+        _serverConfig: Object,
+        /** @type {?string} */
+        _docsBaseUrl: String,
+        _emailsChanged: Boolean,
 
-      /**
+        /**
        * For testing purposes.
        */
-      _loadingPromise: Object,
+        _loadingPromise: Object,
 
-      _showNumber: Boolean,
+        _showNumber: Boolean,
 
-      _isDark: {
-        type: Boolean,
-        value: false,
-      },
-    },
+        _isDark: {
+          type: Boolean,
+          value: false,
+        },
+      };
+    }
 
-    behaviors: [
-      Gerrit.DocsUrlBehavior,
-      Gerrit.ChangeTableBehavior,
-      Gerrit.FireBehavior,
-    ],
-
-    observers: [
-      '_handlePrefsChanged(_localPrefs.*)',
-      '_handleMenuChanged(_localMenu.splices)',
-      '_handleChangeTableChanged(_localChangeTableColumns, _showNumber)',
-    ],
+    static get observers() {
+      return [
+        '_handlePrefsChanged(_localPrefs.*)',
+        '_handleMenuChanged(_localMenu.splices)',
+        '_handleChangeTableChanged(_localChangeTableColumns, _showNumber)',
+      ];
+    }
 
     attached() {
+      super.attached();
       // Polymer 2: anchor tag won't work on shadow DOM
       // we need to manually calling scrollIntoView when hash changed
       this.listen(window, 'location-change', '_handleLocationChange');
@@ -220,11 +229,12 @@
         // Handle anchor tag for initial load
         this._handleLocationChange();
       });
-    },
+    }
 
     detached() {
+      super.detached();
       this.unlisten(window, 'location-change', '_handleLocationChange');
-    },
+    }
 
     _handleLocationChange() {
       // Handle anchor tag after dom attached
@@ -236,25 +246,25 @@
           elem.scrollIntoView();
         }
       }
-    },
+    }
 
     reloadAccountDetail() {
       Promise.all([
         this.$.accountInfo.loadData(),
         this.$.emailEditor.loadData(),
       ]);
-    },
+    }
 
     _isLoading() {
       return this._loading || this._loading === undefined;
-    },
+    }
 
     _copyPrefs(to, from) {
       for (let i = 0; i < PREFS_SECTION_FIELDS.length; i++) {
         this.set([to, PREFS_SECTION_FIELDS[i]],
             this[from][PREFS_SECTION_FIELDS[i]]);
       }
-    },
+    }
 
     _cloneMenu(prefs) {
       const menu = [];
@@ -266,7 +276,7 @@
         });
       }
       this._localMenu = menu;
-    },
+    }
 
     _cloneChangeTableColumns() {
       let columns = this.getVisibleColumns(this.prefs.change_table);
@@ -279,56 +289,56 @@
             this.prefs.change_table);
       }
       this._localChangeTableColumns = columns;
-    },
+    }
 
     _formatChangeTableColumns(changeTableArray) {
       return changeTableArray.map(item => {
         return {column: item};
       });
-    },
+    }
 
     _handleChangeTableChanged() {
       if (this._isLoading()) { return; }
       this._changeTableChanged = true;
-    },
+    }
 
     _handlePrefsChanged(prefs) {
       if (this._isLoading()) { return; }
       this._prefsChanged = true;
-    },
+    }
 
     _handleRelativeDateInChangeTable() {
       this.set('_localPrefs.relative_date_in_change_table',
           this.$.relativeDateInChangeTable.checked);
-    },
+    }
 
     _handleShowSizeBarsInFileListChanged() {
       this.set('_localPrefs.size_bar_in_change_table',
           this.$.showSizeBarsInFileList.checked);
-    },
+    }
 
     _handlePublishCommentsOnPushChanged() {
       this.set('_localPrefs.publish_comments_on_push',
           this.$.publishCommentsOnPush.checked);
-    },
+    }
 
     _handleWorkInProgressByDefault() {
       this.set('_localPrefs.work_in_progress_by_default',
           this.$.workInProgressByDefault.checked);
-    },
+    }
 
     _handleInsertSignedOff() {
       this.set('_localPrefs.signed_off_by', this.$.insertSignedOff.checked);
-    },
+    }
 
     _handleMenuChanged() {
       if (this._isLoading()) { return; }
       this._menuChanged = true;
-    },
+    }
 
     _handleSaveAccountInfo() {
       this.$.accountInfo.save();
-    },
+    }
 
     _handleSavePreferences() {
       this._copyPrefs('prefs', '_localPrefs');
@@ -336,7 +346,7 @@
       return this.$.restAPI.savePreferences(this.prefs).then(() => {
         this._prefsChanged = false;
       });
-    },
+    }
 
     _handleSaveChangeTable() {
       this.set('prefs.change_table', this._localChangeTableColumns);
@@ -345,15 +355,15 @@
       return this.$.restAPI.savePreferences(this.prefs).then(() => {
         this._changeTableChanged = false;
       });
-    },
+    }
 
     _handleSaveDiffPreferences() {
       this.$.diffPrefs.save();
-    },
+    }
 
     _handleSaveEditPreferences() {
       this.$.editPrefs.save();
-    },
+    }
 
     _handleSaveMenu() {
       this.set('prefs.my', this._localMenu);
@@ -361,7 +371,7 @@
       return this.$.restAPI.savePreferences(this.prefs).then(() => {
         this._menuChanged = false;
       });
-    },
+    }
 
     _handleResetMenuButton() {
       return this.$.restAPI.getDefaultPreferences().then(data => {
@@ -369,34 +379,34 @@
           this._cloneMenu(data.my);
         }
       });
-    },
+    }
 
     _handleSaveWatchedProjects() {
       this.$.watchedProjectsEditor.save();
-    },
+    }
 
     _computeHeaderClass(changed) {
       return changed ? 'edited' : '';
-    },
+    }
 
     _handleSaveEmails() {
       this.$.emailEditor.save();
-    },
+    }
 
     _handleNewEmailKeydown(e) {
       if (e.keyCode === 13) { // Enter
         e.stopPropagation();
         this._handleAddEmailButton();
       }
-    },
+    }
 
     _isNewEmailValid(newEmail) {
       return newEmail && newEmail.includes('@');
-    },
+    }
 
     _computeAddEmailButtonEnabled(newEmail, addingEmail) {
       return this._isNewEmailValid(newEmail) && !addingEmail;
-    },
+    }
 
     _handleAddEmailButton() {
       if (!this._isNewEmailValid(this._newEmail)) { return; }
@@ -411,7 +421,7 @@
         this._lastSentVerificationEmail = this._newEmail;
         this._newEmail = '';
       });
-    },
+    }
 
     _getFilterDocsLink(docsBaseUrl) {
       let base = docsBaseUrl;
@@ -423,7 +433,7 @@
       base = base.replace(TRAILING_SLASH_PATTERN, '');
 
       return base + GERRIT_DOCS_FILTER_PATH;
-    },
+    }
 
     _handleToggleDark() {
       if (this._isDark) {
@@ -439,7 +449,7 @@
       this.async(() => {
         window.location.reload();
       }, 1);
-    },
+    }
 
     _showHttpAuth(config) {
       if (config && config.auth &&
@@ -449,6 +459,8 @@
       }
 
       return false;
-    },
-  });
+    }
+  }
+
+  customElements.define(GrSettingsView.is, GrSettingsView);
 })();
diff --git a/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-view_test.html b/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-view_test.html
index 6dcf124..24ee69b 100644
--- a/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-view_test.html
+++ b/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-view_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-settings-view</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>
diff --git a/polygerrit-ui/app/elements/settings/gr-ssh-editor/gr-ssh-editor.js b/polygerrit-ui/app/elements/settings/gr-ssh-editor/gr-ssh-editor.js
index 874173a..6e17817 100644
--- a/polygerrit-ui/app/elements/settings/gr-ssh-editor/gr-ssh-editor.js
+++ b/polygerrit-ui/app/elements/settings/gr-ssh-editor/gr-ssh-editor.js
@@ -17,33 +17,37 @@
 (function() {
   'use strict';
 
-  Polymer({
-    is: 'gr-ssh-editor',
+  class GrSshEditor extends Polymer.GestureEventListeners(
+      Polymer.LegacyElementMixin(
+          Polymer.Element)) {
+    static get is() { return 'gr-ssh-editor'; }
 
-    properties: {
-      hasUnsavedChanges: {
-        type: Boolean,
-        value: false,
-        notify: true,
-      },
-      _keys: Array,
-      /** @type {?} */
-      _keyToView: Object,
-      _newKey: {
-        type: String,
-        value: '',
-      },
-      _keysToRemove: {
-        type: Array,
-        value() { return []; },
-      },
-    },
+    static get properties() {
+      return {
+        hasUnsavedChanges: {
+          type: Boolean,
+          value: false,
+          notify: true,
+        },
+        _keys: Array,
+        /** @type {?} */
+        _keyToView: Object,
+        _newKey: {
+          type: String,
+          value: '',
+        },
+        _keysToRemove: {
+          type: Array,
+          value() { return []; },
+        },
+      };
+    }
 
     loadData() {
       return this.$.restAPI.getAccountSSHKeys().then(keys => {
         this._keys = keys;
       });
-    },
+    }
 
     save() {
       const promises = this._keysToRemove.map(key => {
@@ -54,22 +58,22 @@
         this._keysToRemove = [];
         this.hasUnsavedChanges = false;
       });
-    },
+    }
 
     _getStatusLabel(isValid) {
       return isValid ? 'Valid' : 'Invalid';
-    },
+    }
 
     _showKey(e) {
       const el = Polymer.dom(e).localTarget;
       const index = parseInt(el.getAttribute('data-index'), 10);
       this._keyToView = this._keys[index];
       this.$.viewKeyOverlay.open();
-    },
+    }
 
     _closeOverlay() {
       this.$.viewKeyOverlay.close();
-    },
+    }
 
     _handleDeleteKey(e) {
       const el = Polymer.dom(e).localTarget;
@@ -77,7 +81,7 @@
       this.push('_keysToRemove', this._keys[index]);
       this.splice('_keys', index, 1);
       this.hasUnsavedChanges = true;
-    },
+    }
 
     _handleAddKey() {
       this.$.addButton.disabled = true;
@@ -91,10 +95,12 @@
             this.$.addButton.disabled = false;
             this.$.newKey.disabled = false;
           });
-    },
+    }
 
     _computeAddButtonDisabled(newKey) {
       return !newKey.length;
-    },
-  });
+    }
+  }
+
+  customElements.define(GrSshEditor.is, GrSshEditor);
 })();
diff --git a/polygerrit-ui/app/elements/settings/gr-ssh-editor/gr-ssh-editor_test.html b/polygerrit-ui/app/elements/settings/gr-ssh-editor/gr-ssh-editor_test.html
index d313f5a..912c1fd 100644
--- a/polygerrit-ui/app/elements/settings/gr-ssh-editor/gr-ssh-editor_test.html
+++ b/polygerrit-ui/app/elements/settings/gr-ssh-editor/gr-ssh-editor_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-ssh-editor</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>
diff --git a/polygerrit-ui/app/elements/settings/gr-watched-projects-editor/gr-watched-projects-editor.js b/polygerrit-ui/app/elements/settings/gr-watched-projects-editor/gr-watched-projects-editor.js
index a40094d..f96aa76 100644
--- a/polygerrit-ui/app/elements/settings/gr-watched-projects-editor/gr-watched-projects-editor.js
+++ b/polygerrit-ui/app/elements/settings/gr-watched-projects-editor/gr-watched-projects-editor.js
@@ -25,34 +25,38 @@
     {name: 'Abandons', key: 'notify_abandoned_changes'},
   ];
 
-  Polymer({
-    is: 'gr-watched-projects-editor',
+  class GrWatchedProjectsEditor extends Polymer.GestureEventListeners(
+      Polymer.LegacyElementMixin(
+          Polymer.Element)) {
+    static get is() { return 'gr-watched-projects-editor'; }
 
-    properties: {
-      hasUnsavedChanges: {
-        type: Boolean,
-        value: false,
-        notify: true,
-      },
-
-      _projects: Array,
-      _projectsToRemove: {
-        type: Array,
-        value() { return []; },
-      },
-      _query: {
-        type: Function,
-        value() {
-          return this._getProjectSuggestions.bind(this);
+    static get properties() {
+      return {
+        hasUnsavedChanges: {
+          type: Boolean,
+          value: false,
+          notify: true,
         },
-      },
-    },
+
+        _projects: Array,
+        _projectsToRemove: {
+          type: Array,
+          value() { return []; },
+        },
+        _query: {
+          type: Function,
+          value() {
+            return this._getProjectSuggestions.bind(this);
+          },
+        },
+      };
+    }
 
     loadData() {
       return this.$.restAPI.getWatchedProjects().then(projs => {
         this._projects = projs;
       });
-    },
+    }
 
     save() {
       let deletePromise;
@@ -72,19 +76,19 @@
             this._projectsToRemove = [];
             this.hasUnsavedChanges = false;
           });
-    },
+    }
 
     _getTypes() {
       return NOTIFICATION_TYPES;
-    },
+    }
 
     _getTypeCount() {
       return this._getTypes().length;
-    },
+    }
 
     _computeCheckboxChecked(project, key) {
       return project.hasOwnProperty(key);
-    },
+    }
 
     _getProjectSuggestions(input) {
       return this.$.restAPI.getSuggestedProjects(input)
@@ -99,7 +103,7 @@
             }
             return projects;
           });
-    },
+    }
 
     _handleRemoveProject(e) {
       const el = Polymer.dom(e).localTarget;
@@ -108,7 +112,7 @@
       this.splice('_projects', index, 1);
       this.push('_projectsToRemove', project);
       this.hasUnsavedChanges = true;
-    },
+    }
 
     _canAddProject(project, text, filter) {
       if ((!project || !project.id) && !text) { return false; }
@@ -126,7 +130,7 @@
       }
 
       return true;
-    },
+    }
 
     _getNewProjectIndex(name, filter) {
       let i;
@@ -138,7 +142,7 @@
         }
       }
       return i;
-    },
+    }
 
     _handleAddProject() {
       const newProject = this.$.newProject.value;
@@ -158,7 +162,7 @@
       this.$.newProject.clear();
       this.$.newFilter.bindValue = '';
       this.hasUnsavedChanges = true;
-    },
+    }
 
     _handleCheckboxChange(e) {
       const el = Polymer.dom(e).localTarget;
@@ -167,11 +171,13 @@
       const checked = el.checked;
       this.set(['_projects', index, key], !!checked);
       this.hasUnsavedChanges = true;
-    },
+    }
 
     _handleNotifCellClick(e) {
       const checkbox = Polymer.dom(e.target).querySelector('input');
       if (checkbox) { checkbox.click(); }
-    },
-  });
+    }
+  }
+
+  customElements.define(GrWatchedProjectsEditor.is, GrWatchedProjectsEditor);
 })();
diff --git a/polygerrit-ui/app/elements/settings/gr-watched-projects-editor/gr-watched-projects-editor_test.html b/polygerrit-ui/app/elements/settings/gr-watched-projects-editor/gr-watched-projects-editor_test.html
index 7a238ec..4bdb00f 100644
--- a/polygerrit-ui/app/elements/settings/gr-watched-projects-editor/gr-watched-projects-editor_test.html
+++ b/polygerrit-ui/app/elements/settings/gr-watched-projects-editor/gr-watched-projects-editor_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-settings-view</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>
diff --git a/polygerrit-ui/app/elements/shared/gr-account-chip/gr-account-chip.js b/polygerrit-ui/app/elements/shared/gr-account-chip/gr-account-chip.js
index 10c876d..27b1cc4 100644
--- a/polygerrit-ui/app/elements/shared/gr-account-chip/gr-account-chip.js
+++ b/polygerrit-ui/app/elements/shared/gr-account-chip/gr-account-chip.js
@@ -14,13 +14,18 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-
 (function() {
   'use strict';
 
-  Polymer({
-    is: 'gr-account-chip',
-
+  /**
+    * @appliesMixin Gerrit.FireMixin
+    */
+  class GrAccountChip extends Polymer.mixinBehaviors( [
+    Gerrit.FireBehavior,
+  ], Polymer.GestureEventListeners(
+      Polymer.LegacyElementMixin(
+          Polymer.Element))) {
+    static get is() { return 'gr-account-chip'; }
     /**
      * Fired to indicate a key was pressed while this chip was focused.
      *
@@ -34,51 +39,52 @@
      * @event remove
      */
 
-    properties: {
-      account: Object,
-      additionalText: String,
-      disabled: {
-        type: Boolean,
-        value: false,
-        reflectToAttribute: true,
-      },
-      removable: {
-        type: Boolean,
-        value: false,
-      },
-      showAvatar: {
-        type: Boolean,
-        reflectToAttribute: true,
-      },
-      transparentBackground: {
-        type: Boolean,
-        value: false,
-      },
-    },
-
-    behaviors: [
-      Gerrit.FireBehavior,
-    ],
+    static get properties() {
+      return {
+        account: Object,
+        additionalText: String,
+        disabled: {
+          type: Boolean,
+          value: false,
+          reflectToAttribute: true,
+        },
+        removable: {
+          type: Boolean,
+          value: false,
+        },
+        showAvatar: {
+          type: Boolean,
+          reflectToAttribute: true,
+        },
+        transparentBackground: {
+          type: Boolean,
+          value: false,
+        },
+      };
+    }
 
     ready() {
+      super.ready();
       this._getHasAvatars().then(hasAvatars => {
         this.showAvatar = hasAvatars;
       });
-    },
+    }
 
     _getBackgroundClass(transparent) {
       return transparent ? 'transparentBackground' : '';
-    },
+    }
 
     _handleRemoveTap(e) {
       e.preventDefault();
       this.fire('remove', {account: this.account});
-    },
+    }
 
     _getHasAvatars() {
       return this.$.restAPI.getConfig().then(cfg => {
         return Promise.resolve(!!(cfg && cfg.plugin && cfg.plugin.has_avatars));
       });
-    },
-  });
+    }
+  }
+
+  customElements.define(GrAccountChip.is, GrAccountChip);
 })();
diff --git a/polygerrit-ui/app/elements/shared/gr-account-entry/gr-account-entry.js b/polygerrit-ui/app/elements/shared/gr-account-entry/gr-account-entry.js
index b2e0973..0ed3f19 100644
--- a/polygerrit-ui/app/elements/shared/gr-account-entry/gr-account-entry.js
+++ b/polygerrit-ui/app/elements/shared/gr-account-entry/gr-account-entry.js
@@ -21,9 +21,10 @@
    * gr-account-entry is an element for entering account
    * and/or group with autocomplete support.
    */
-  Polymer({
-    is: 'gr-account-entry',
-
+  class GrAccountEntry extends Polymer.GestureEventListeners(
+      Polymer.LegacyElementMixin(
+          Polymer.Element)) {
+    static get is() { return 'gr-account-entry'; }
     /**
      * Fired when an account is entered.
      *
@@ -37,65 +38,70 @@
      *
      * @event account-text-changed
      */
-    properties: {
-      allowAnyInput: Boolean,
-      borderless: Boolean,
-      placeholder: String,
 
-      // suggestFrom = 0 to enable default suggestions.
-      suggestFrom: {
-        type: Number,
-        value: 0,
-      },
+    static get properties() {
+      return {
+        allowAnyInput: Boolean,
+        borderless: Boolean,
+        placeholder: String,
 
-      /** @type {!function(string): !Promise<Array<{name, value}>>} */
-      querySuggestions: {
-        type: Function,
-        notify: true,
-        value() {
-          return input => Promise.resolve([]);
+        // suggestFrom = 0 to enable default suggestions.
+        suggestFrom: {
+          type: Number,
+          value: 0,
         },
-      },
 
-      _config: Object,
-      /** The value of the autocomplete entry. */
-      _inputText: {
-        type: String,
-        observer: '_inputTextChanged',
-      },
+        /** @type {!function(string): !Promise<Array<{name, value}>>} */
+        querySuggestions: {
+          type: Function,
+          notify: true,
+          value() {
+            return input => Promise.resolve([]);
+          },
+        },
 
-    },
+        _config: Object,
+        /** The value of the autocomplete entry. */
+        _inputText: {
+          type: String,
+          observer: '_inputTextChanged',
+        },
+
+      };
+    }
 
     get focusStart() {
       return this.$.input.focusStart;
-    },
+    }
 
     focus() {
       this.$.input.focus();
-    },
+    }
 
     clear() {
       this.$.input.clear();
-    },
+    }
 
     setText(text) {
       this.$.input.setText(text);
-    },
+    }
 
     getText() {
       return this.$.input.text;
-    },
+    }
 
     _handleInputCommit(e) {
       this.fire('add', {value: e.detail.value});
       this.$.input.focus();
-    },
+    }
 
     _inputTextChanged(text) {
       if (text.length && this.allowAnyInput) {
         this.dispatchEvent(new CustomEvent(
             'account-text-changed', {bubbles: true, composed: true}));
       }
-    },
-  });
+    }
+  }
+
+  customElements.define(GrAccountEntry.is, GrAccountEntry);
 })();
diff --git a/polygerrit-ui/app/elements/shared/gr-account-entry/gr-account-entry_test.html b/polygerrit-ui/app/elements/shared/gr-account-entry/gr-account-entry_test.html
index 6896af9..0bac7e0 100644
--- a/polygerrit-ui/app/elements/shared/gr-account-entry/gr-account-entry_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-account-entry/gr-account-entry_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-account-entry</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>
diff --git a/polygerrit-ui/app/elements/shared/gr-account-label/gr-account-label.js b/polygerrit-ui/app/elements/shared/gr-account-label/gr-account-label.js
index 418d2ea..3d96421 100644
--- a/polygerrit-ui/app/elements/shared/gr-account-label/gr-account-label.js
+++ b/polygerrit-ui/app/elements/shared/gr-account-label/gr-account-label.js
@@ -17,58 +17,65 @@
 (function() {
   'use strict';
 
-  Polymer({
-    is: 'gr-account-label',
+  /**
+    * @appliesMixin Gerrit.DisplayNameMixin
+    * @appliesMixin Gerrit.TooltipMixin
+    */
+  class GrAccountLabel extends Polymer.mixinBehaviors( [
+    Gerrit.DisplayNameBehavior,
+    Gerrit.TooltipBehavior,
+  ], Polymer.GestureEventListeners(
+      Polymer.LegacyElementMixin(
+          Polymer.Element))) {
+    static get is() { return 'gr-account-label'; }
 
-    properties: {
+    static get properties() {
+      return {
       /**
        * @type {{ name: string, status: string }}
        */
-      account: Object,
-      avatarImageSize: {
-        type: Number,
-        value: 32,
-      },
-      title: {
-        type: String,
-        reflectToAttribute: true,
-        computed: '_computeAccountTitle(account, additionalText)',
-      },
-      additionalText: String,
-      hasTooltip: {
-        type: Boolean,
-        reflectToAttribute: true,
-        computed: '_computeHasTooltip(account)',
-      },
-      hideAvatar: {
-        type: Boolean,
-        value: false,
-      },
-      _serverConfig: {
-        type: Object,
-        value: null,
-      },
-    },
-
-    behaviors: [
-      Gerrit.DisplayNameBehavior,
-      Gerrit.TooltipBehavior,
-    ],
+        account: Object,
+        avatarImageSize: {
+          type: Number,
+          value: 32,
+        },
+        title: {
+          type: String,
+          reflectToAttribute: true,
+          computed: '_computeAccountTitle(account, additionalText)',
+        },
+        additionalText: String,
+        hasTooltip: {
+          type: Boolean,
+          reflectToAttribute: true,
+          computed: '_computeHasTooltip(account)',
+        },
+        hideAvatar: {
+          type: Boolean,
+          value: false,
+        },
+        _serverConfig: {
+          type: Object,
+          value: null,
+        },
+      };
+    }
 
     ready() {
+      super.ready();
       if (!this.additionalText) { this.additionalText = ''; }
       this.$.restAPI.getConfig()
           .then(config => { this._serverConfig = config; });
-    },
+    }
 
     _computeName(account, config) {
       return this.getUserName(config, account, false);
-    },
+    }
 
     _computeStatusTextLength(account, config) {
       // 35 as the max length of the name + status
       return Math.max(10, 35 - this._computeName(account, config).length);
-    },
+    }
 
     _computeAccountTitle(account, tooltip) {
       // Polymer 2: check for undefined
@@ -98,12 +105,12 @@
       }
 
       return result;
-    },
+    }
 
     _computeShowEmailClass(account) {
       if (!account || account.name || !account.email) { return ''; }
       return 'showEmail';
-    },
+    }
 
     _computeEmailStr(account) {
       if (!account || !account.email) {
@@ -113,11 +120,13 @@
         return '(' + account.email + ')';
       }
       return account.email;
-    },
+    }
 
     _computeHasTooltip(account) {
       // If an account has loaded to fire this method, then set to true.
       return !!account;
-    },
-  });
+    }
+  }
+
+  customElements.define(GrAccountLabel.is, GrAccountLabel);
 })();
diff --git a/polygerrit-ui/app/elements/shared/gr-account-label/gr-account-label_test.html b/polygerrit-ui/app/elements/shared/gr-account-label/gr-account-label_test.html
index f0620d3..f369ae2 100644
--- a/polygerrit-ui/app/elements/shared/gr-account-label/gr-account-label_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-account-label/gr-account-label_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-account-label</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>
diff --git a/polygerrit-ui/app/elements/shared/gr-account-link/gr-account-link.js b/polygerrit-ui/app/elements/shared/gr-account-link/gr-account-link.js
index faaf9c3..0764669 100644
--- a/polygerrit-ui/app/elements/shared/gr-account-link/gr-account-link.js
+++ b/polygerrit-ui/app/elements/shared/gr-account-link/gr-account-link.js
@@ -17,27 +17,34 @@
 (function() {
   'use strict';
 
-  Polymer({
-    is: 'gr-account-link',
+  /**
+    * @appliesMixin Gerrit.BaseUrlMixin
+    */
+  class GrAccountLink extends Polymer.mixinBehaviors( [
+    Gerrit.BaseUrlBehavior,
+  ], Polymer.GestureEventListeners(
+      Polymer.LegacyElementMixin(
+          Polymer.Element))) {
+    static get is() { return 'gr-account-link'; }
 
-    properties: {
-      additionalText: String,
-      account: Object,
-      avatarImageSize: {
-        type: Number,
-        value: 32,
-      },
-    },
-
-    behaviors: [
-      Gerrit.BaseUrlBehavior,
-    ],
+    static get properties() {
+      return {
+        additionalText: String,
+        account: Object,
+        avatarImageSize: {
+          type: Number,
+          value: 32,
+        },
+      };
+    }
 
     _computeOwnerLink(account) {
       if (!account) { return; }
       return Gerrit.Nav.getUrlForOwner(
           account.email || account.username || account.name ||
           account._account_id);
-    },
-  });
+    }
+  }
+
+  customElements.define(GrAccountLink.is, GrAccountLink);
 })();
diff --git a/polygerrit-ui/app/elements/shared/gr-account-link/gr-account-link_test.html b/polygerrit-ui/app/elements/shared/gr-account-link/gr-account-link_test.html
index 134c579..e41304f 100644
--- a/polygerrit-ui/app/elements/shared/gr-account-link/gr-account-link_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-account-link/gr-account-link_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-account-link</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>
diff --git a/polygerrit-ui/app/elements/shared/gr-account-list/gr-account-list.js b/polygerrit-ui/app/elements/shared/gr-account-list/gr-account-list.js
index 9897105..48dddb0 100644
--- a/polygerrit-ui/app/elements/shared/gr-account-list/gr-account-list.js
+++ b/polygerrit-ui/app/elements/shared/gr-account-list/gr-account-list.js
@@ -19,95 +19,101 @@
 
   const VALID_EMAIL_ALERT = 'Please input a valid email.';
 
-  Polymer({
-    is: 'gr-account-list',
-
+  /**
+    * @appliesMixin Gerrit.FireMixin
+    */
+  class GrAccountList extends Polymer.mixinBehaviors( [
+    // Used in the tests for gr-account-list and other elements tests.
+    Gerrit.FireBehavior,
+  ], Polymer.GestureEventListeners(
+      Polymer.LegacyElementMixin(
+          Polymer.Element))) {
+    static get is() { return 'gr-account-list'; }
     /**
      * Fired when user inputs an invalid email address.
      *
      * @event show-alert
      */
 
-    properties: {
-      accounts: {
-        type: Array,
-        value() { return []; },
-        notify: true,
-      },
-      change: Object,
-      filter: Function,
-      placeholder: String,
-      disabled: {
-        type: Function,
-        value: false,
-      },
+    static get properties() {
+      return {
+        accounts: {
+          type: Array,
+          value() { return []; },
+          notify: true,
+        },
+        change: Object,
+        filter: Function,
+        placeholder: String,
+        disabled: {
+          type: Function,
+          value: false,
+        },
 
-      /**
+        /**
        * Returns suggestions and convert them to list item
        * @type {Gerrit.GrSuggestionsProvider}
        */
-      suggestionsProvider: {
-        type: Object,
-      },
+        suggestionsProvider: {
+          type: Object,
+        },
 
-      /**
+        /**
        * Needed for template checking since value is initially set to null.
        * @type {?Object} */
-      pendingConfirmation: {
-        type: Object,
-        value: null,
-        notify: true,
-      },
-      readonly: {
-        type: Boolean,
-        value: false,
-      },
-      /**
+        pendingConfirmation: {
+          type: Object,
+          value: null,
+          notify: true,
+        },
+        readonly: {
+          type: Boolean,
+          value: false,
+        },
+        /**
        * When true, allows for non-suggested inputs to be added.
        */
-      allowAnyInput: {
-        type: Boolean,
-        value: false,
-      },
+        allowAnyInput: {
+          type: Boolean,
+          value: false,
+        },
 
-      /**
+        /**
        * Array of values (groups/accounts) that are removable. When this prop is
        * undefined, all values are removable.
        */
-      removableValues: Array,
-      maxCount: {
-        type: Number,
-        value: 0,
-      },
+        removableValues: Array,
+        maxCount: {
+          type: Number,
+          value: 0,
+        },
 
-      /** Returns suggestion items
+        /** Returns suggestion items
       * @type {!function(string): Promise<Array<Gerrit.GrSuggestionItem>>}
       */
-      _querySuggestions: {
-        type: Function,
-        value() {
-          return this._getSuggestions.bind(this);
+        _querySuggestions: {
+          type: Function,
+          value() {
+            return this._getSuggestions.bind(this);
+          },
         },
-      },
-    },
+      };
+    }
 
-    behaviors: [
-      // Used in the tests for gr-account-list and other elements tests.
-      Gerrit.FireBehavior,
-    ],
-
-    listeners: {
-      remove: '_handleRemove',
-    },
+    created() {
+      super.created();
+      this.addEventListener('remove',
+          e => this._handleRemove(e));
+    }
 
     get accountChips() {
       return Array.from(
           Polymer.dom(this.root).querySelectorAll('gr-account-chip'));
-    },
+    }
 
     get focusStart() {
       return this.$.entry.focusStart;
-    },
+    }
 
     _getSuggestions(input) {
       const provider = this.suggestionsProvider;
@@ -122,11 +128,11 @@
         return suggestions.map(suggestion =>
           provider.makeSuggestionItem(suggestion));
       });
-    },
+    }
 
     _handleAdd(e) {
       this._addAccountItem(e.detail.value);
-    },
+    }
 
     _addAccountItem(item) {
       // Append new account or group to the accounts property. We add our own
@@ -162,14 +168,14 @@
       }
       this.pendingConfirmation = null;
       return true;
-    },
+    }
 
     confirmGroup(group) {
       group = Object.assign(
           {}, group, {confirmed: true, _pendingAdd: true, _group: true});
       this.push('accounts', group);
       this.pendingConfirmation = null;
-    },
+    }
 
     _computeChipClass(account) {
       const classes = [];
@@ -180,7 +186,7 @@
         classes.push('pendingAdd');
       }
       return classes.join(' ');
-    },
+    }
 
     _accountMatches(a, b) {
       if (a && b) {
@@ -192,7 +198,7 @@
         }
       }
       return a === b;
-    },
+    }
 
     _computeRemovable(account, readonly) {
       if (readonly) { return false; }
@@ -205,13 +211,13 @@
         return !!account._pendingAdd;
       }
       return true;
-    },
+    }
 
     _handleRemove(e) {
       const toRemove = e.detail.account;
       this._removeAccount(toRemove);
       this.$.entry.focus();
-    },
+    }
 
     _removeAccount(toRemove) {
       if (!toRemove || !this._computeRemovable(toRemove, this.readonly)) {
@@ -231,12 +237,12 @@
         }
       }
       console.warn('received remove event for missing account', toRemove);
-    },
+    }
 
     _getNativeInput(paperInput) {
       // In Polymer 2 inputElement isn't nativeInput anymore
       return paperInput.$.nativeInput || paperInput.inputElement;
-    },
+    }
 
     _handleInputKeydown(e) {
       const input = this._getNativeInput(e.detail.input);
@@ -254,7 +260,7 @@
           }
           break;
       }
-    },
+    }
 
     _handleChipKeydown(e) {
       const chip = e.target;
@@ -292,7 +298,7 @@
           }
           break;
       }
-    },
+    }
 
     /**
      * Submit the text of the entry as a reviewer value, if it exists. If it is
@@ -308,7 +314,7 @@
       const wasSubmitted = this._addAccountItem(text);
       if (wasSubmitted) { this.$.entry.clear(); }
       return wasSubmitted;
-    },
+    }
 
     additions() {
       return this.accounts.filter(account => {
@@ -320,10 +326,12 @@
           return {account};
         }
       });
-    },
+    }
 
     _computeEntryHidden(maxCount, accountsRecord, readonly) {
       return (maxCount && maxCount <= accountsRecord.base.length) || readonly;
-    },
-  });
+    }
+  }
+
+  customElements.define(GrAccountList.is, GrAccountList);
 })();
diff --git a/polygerrit-ui/app/elements/shared/gr-account-list/gr-account-list_test.html b/polygerrit-ui/app/elements/shared/gr-account-list/gr-account-list_test.html
index 202ddf6..9521c5f 100644
--- a/polygerrit-ui/app/elements/shared/gr-account-list/gr-account-list_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-account-list/gr-account-list_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-account-list</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>
diff --git a/polygerrit-ui/app/elements/shared/gr-alert/gr-alert.js b/polygerrit-ui/app/elements/shared/gr-alert/gr-alert.js
index e7c8b2c..433a57b 100644
--- a/polygerrit-ui/app/elements/shared/gr-alert/gr-alert.js
+++ b/polygerrit-ui/app/elements/shared/gr-alert/gr-alert.js
@@ -17,46 +17,51 @@
 (function() {
   'use strict';
 
-  Polymer({
-    is: 'gr-alert',
-
+  class GrAlert extends Polymer.GestureEventListeners(
+      Polymer.LegacyElementMixin(
+          Polymer.Element)) {
+    static get is() { return 'gr-alert'; }
     /**
      * Fired when the action button is pressed.
      *
      * @event action
      */
 
-    properties: {
-      text: String,
-      actionText: String,
-      shown: {
-        type: Boolean,
-        value: true,
-        readOnly: true,
-        reflectToAttribute: true,
-      },
-      toast: {
-        type: Boolean,
-        value: true,
-        reflectToAttribute: true,
-      },
+    static get properties() {
+      return {
+        text: String,
+        actionText: String,
+        shown: {
+          type: Boolean,
+          value: true,
+          readOnly: true,
+          reflectToAttribute: true,
+        },
+        toast: {
+          type: Boolean,
+          value: true,
+          reflectToAttribute: true,
+        },
 
-      _hideActionButton: Boolean,
-      _boundTransitionEndHandler: {
-        type: Function,
-        value() { return this._handleTransitionEnd.bind(this); },
-      },
-      _actionCallback: Function,
-    },
+        _hideActionButton: Boolean,
+        _boundTransitionEndHandler: {
+          type: Function,
+          value() { return this._handleTransitionEnd.bind(this); },
+        },
+        _actionCallback: Function,
+      };
+    }
 
     attached() {
+      super.attached();
       this.addEventListener('transitionend', this._boundTransitionEndHandler);
-    },
+    }
 
     detached() {
+      super.detached();
       this.removeEventListener('transitionend',
           this._boundTransitionEndHandler);
-    },
+    }
 
     show(text, opt_actionText, opt_actionCallback) {
       this.text = text;
@@ -65,31 +70,33 @@
       this._actionCallback = opt_actionCallback;
       Gerrit.getRootElement().appendChild(this);
       this._setShown(true);
-    },
+    }
 
     hide() {
       this._setShown(false);
       if (this._hasZeroTransitionDuration()) {
         Gerrit.getRootElement().removeChild(this);
       }
-    },
+    }
 
     _hasZeroTransitionDuration() {
       const style = window.getComputedStyle(this);
       // transitionDuration is always given in seconds.
       const duration = Math.round(parseFloat(style.transitionDuration) * 100);
       return duration === 0;
-    },
+    }
 
     _handleTransitionEnd(e) {
       if (this.shown) { return; }
 
       Gerrit.getRootElement().removeChild(this);
-    },
+    }
 
     _handleActionTap(e) {
       e.preventDefault();
       if (this._actionCallback) { this._actionCallback(); }
-    },
-  });
+    }
+  }
+
+  customElements.define(GrAlert.is, GrAlert);
 })();
diff --git a/polygerrit-ui/app/elements/shared/gr-alert/gr-alert_test.html b/polygerrit-ui/app/elements/shared/gr-alert/gr-alert_test.html
index 2338d55..bfcc431 100644
--- a/polygerrit-ui/app/elements/shared/gr-alert/gr-alert_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-alert/gr-alert_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-alert</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>
diff --git a/polygerrit-ui/app/elements/shared/gr-autocomplete-dropdown/gr-autocomplete-dropdown.html b/polygerrit-ui/app/elements/shared/gr-autocomplete-dropdown/gr-autocomplete-dropdown.html
index 9208068..64950f2 100644
--- a/polygerrit-ui/app/elements/shared/gr-autocomplete-dropdown/gr-autocomplete-dropdown.html
+++ b/polygerrit-ui/app/elements/shared/gr-autocomplete-dropdown/gr-autocomplete-dropdown.html
@@ -22,6 +22,7 @@
 <link rel="import" href="/bower_components/iron-dropdown/iron-dropdown.html">
 <link rel="import" href="/bower_components/iron-fit-behavior/iron-fit-behavior.html">
 <link rel="import" href="../../shared/gr-cursor-manager/gr-cursor-manager.html">
+<script src="../../../types/polymer-behaviors.js"></script>
 <script src="../../../scripts/rootElement.js"></script>
 <link rel="import" href="../../../styles/shared-styles.html">
 
diff --git a/polygerrit-ui/app/elements/shared/gr-autocomplete-dropdown/gr-autocomplete-dropdown.js b/polygerrit-ui/app/elements/shared/gr-autocomplete-dropdown/gr-autocomplete-dropdown.js
index b8c76ff..707c426 100644
--- a/polygerrit-ui/app/elements/shared/gr-autocomplete-dropdown/gr-autocomplete-dropdown.js
+++ b/polygerrit-ui/app/elements/shared/gr-autocomplete-dropdown/gr-autocomplete-dropdown.js
@@ -17,9 +17,19 @@
 (function() {
   'use strict';
 
-  Polymer({
-    is: 'gr-autocomplete-dropdown',
-
+  /**
+    * @appliesMixin Gerrit.FireMixin
+    * @appliesMixin Gerrit.KeyboardShortcutMixin
+    * @appliesMixin Polymer.IronFitMixin
+    */
+  class GrAutocompleteDropdown extends Polymer.mixinBehaviors( [
+    Gerrit.FireBehavior,
+    Gerrit.KeyboardShortcutBehavior,
+    Polymer.IronFitBehavior,
+  ], Polymer.GestureEventListeners(
+      Polymer.LegacyElementMixin(
+          Polymer.Element))) {
+    static get is() { return 'gr-autocomplete-dropdown'; }
     /**
      * Fired when the dropdown is closed.
      *
@@ -32,57 +42,55 @@
      * @event item-selected
      */
 
-    properties: {
-      index: Number,
-      isHidden: {
-        type: Boolean,
-        value: true,
-        reflectToAttribute: true,
-      },
-      verticalOffset: {
-        type: Number,
-        value: null,
-      },
-      horizontalOffset: {
-        type: Number,
-        value: null,
-      },
-      suggestions: {
-        type: Array,
-        value: () => [],
-        observer: '_resetCursorStops',
-      },
-      _suggestionEls: Array,
-    },
+    static get properties() {
+      return {
+        index: Number,
+        isHidden: {
+          type: Boolean,
+          value: true,
+          reflectToAttribute: true,
+        },
+        verticalOffset: {
+          type: Number,
+          value: null,
+        },
+        horizontalOffset: {
+          type: Number,
+          value: null,
+        },
+        suggestions: {
+          type: Array,
+          value: () => [],
+          observer: '_resetCursorStops',
+        },
+        _suggestionEls: Array,
+      };
+    }
 
-    behaviors: [
-      Gerrit.FireBehavior,
-      Gerrit.KeyboardShortcutBehavior,
-      Polymer.IronFitBehavior,
-    ],
-
-    keyBindings: {
-      up: '_handleUp',
-      down: '_handleDown',
-      enter: '_handleEnter',
-      esc: '_handleEscape',
-      tab: '_handleTab',
-    },
+    get keyBindings() {
+      return {
+        up: '_handleUp',
+        down: '_handleDown',
+        enter: '_handleEnter',
+        esc: '_handleEscape',
+        tab: '_handleTab',
+      };
+    }
 
     close() {
       this.isHidden = true;
-    },
+    }
 
     open() {
       this.isHidden = false;
       this._resetCursorStops();
       // Refit should run after we call Polymer.flush inside _resetCursorStops
       this.refit();
-    },
+    }
 
     getCurrentText() {
       return this.getCursorTarget().dataset.value;
-    },
+    }
 
     _handleUp(e) {
       if (!this.isHidden) {
@@ -90,7 +98,7 @@
         e.stopPropagation();
         this.cursorUp();
       }
-    },
+    }
 
     _handleDown(e) {
       if (!this.isHidden) {
@@ -98,19 +106,19 @@
         e.stopPropagation();
         this.cursorDown();
       }
-    },
+    }
 
     cursorDown() {
       if (!this.isHidden) {
         this.$.cursor.next();
       }
-    },
+    }
 
     cursorUp() {
       if (!this.isHidden) {
         this.$.cursor.previous();
       }
-    },
+    }
 
     _handleTab(e) {
       e.preventDefault();
@@ -119,7 +127,7 @@
         trigger: 'tab',
         selected: this.$.cursor.target,
       });
-    },
+    }
 
     _handleEnter(e) {
       e.preventDefault();
@@ -128,12 +136,12 @@
         trigger: 'enter',
         selected: this.$.cursor.target,
       });
-    },
+    }
 
     _handleEscape() {
       this._fireClose();
       this.close();
-    },
+    }
 
     _handleClickItem(e) {
       e.preventDefault();
@@ -147,15 +155,15 @@
         trigger: 'click',
         selected,
       });
-    },
+    }
 
     _fireClose() {
       this.fire('dropdown-closed');
-    },
+    }
 
     getCursorTarget() {
       return this.$.cursor.target;
-    },
+    }
 
     _resetCursorStops() {
       if (this.suggestions.length > 0) {
@@ -168,14 +176,16 @@
       } else {
         this._suggestionEls = [];
       }
-    },
+    }
 
     _resetCursorIndex() {
       this.$.cursor.setCursorAtIndex(0);
-    },
+    }
 
     _computeLabelClass(item) {
       return item.label ? '' : 'hide';
-    },
-  });
+    }
+  }
+
+  customElements.define(GrAutocompleteDropdown.is, GrAutocompleteDropdown);
 })();
diff --git a/polygerrit-ui/app/elements/shared/gr-autocomplete-dropdown/gr-autocomplete-dropdown_test.html b/polygerrit-ui/app/elements/shared/gr-autocomplete-dropdown/gr-autocomplete-dropdown_test.html
index a7b59d7..5f2ce94 100644
--- a/polygerrit-ui/app/elements/shared/gr-autocomplete-dropdown/gr-autocomplete-dropdown_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-autocomplete-dropdown/gr-autocomplete-dropdown_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-autocomplete-dropdown</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>
diff --git a/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete.js b/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete.js
index da16e2b..a2c908a 100644
--- a/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete.js
+++ b/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete.js
@@ -20,9 +20,17 @@
   const TOKENIZE_REGEX = /(?:[^\s"]+|"[^"]*")+/g;
   const DEBOUNCE_WAIT_MS = 200;
 
-  Polymer({
-    is: 'gr-autocomplete',
-
+  /**
+    * @appliesMixin Gerrit.FireMixin
+    * @appliesMixin Gerrit.KeyboardShortcutMixin
+    */
+  class GrAutocomplete extends Polymer.mixinBehaviors( [
+    Gerrit.FireBehavior,
+    Gerrit.KeyboardShortcutBehavior,
+  ], Polymer.GestureEventListeners(
+      Polymer.LegacyElementMixin(
+          Polymer.Element))) {
+    static get is() { return 'gr-autocomplete'; }
     /**
      * Fired when a value is chosen.
      *
@@ -42,9 +50,10 @@
      * @event input-keydown
      */
 
-    properties: {
+    static get properties() {
+      return {
 
-      /**
+        /**
        * Query for requesting autocomplete suggestions. The function should
        * accept the input as a string parameter and return a promise. The
        * promise yields an array of suggestion objects with "name", "label",
@@ -55,170 +64,170 @@
        *
        * @type {function(string): Promise<?>}
        */
-      query: {
-        type: Function,
-        value() {
-          return function() {
-            return Promise.resolve([]);
-          };
+        query: {
+          type: Function,
+          value() {
+            return function() {
+              return Promise.resolve([]);
+            };
+          },
         },
-      },
 
-      /**
+        /**
        * The number of characters that must be typed before suggestions are
        * made. If threshold is zero, default suggestions are enabled.
        */
-      threshold: {
-        type: Number,
-        value: 1,
-      },
+        threshold: {
+          type: Number,
+          value: 1,
+        },
 
-      allowNonSuggestedValues: Boolean,
-      borderless: Boolean,
-      disabled: Boolean,
-      showSearchIcon: {
-        type: Boolean,
-        value: false,
-      },
-      // Vertical offset needed for a 1em font-size with no vertical padding.
-      // Inputs with additional padding will need to increase vertical offset.
-      verticalOffset: {
-        type: Number,
-        value: 20,
-      },
+        allowNonSuggestedValues: Boolean,
+        borderless: Boolean,
+        disabled: Boolean,
+        showSearchIcon: {
+          type: Boolean,
+          value: false,
+        },
+        // Vertical offset needed for a 1em font-size with no vertical padding.
+        // Inputs with additional padding will need to increase vertical offset.
+        verticalOffset: {
+          type: Number,
+          value: 20,
+        },
 
-      text: {
-        type: String,
-        value: '',
-        notify: true,
-      },
+        text: {
+          type: String,
+          value: '',
+          notify: true,
+        },
 
-      placeholder: String,
+        placeholder: String,
 
-      clearOnCommit: {
-        type: Boolean,
-        value: false,
-      },
+        clearOnCommit: {
+          type: Boolean,
+          value: false,
+        },
 
-      /**
+        /**
        * When true, tab key autocompletes but does not fire the commit event.
        * When false, tab key not caught, and focus is removed from the element.
        * See Issue 4556, Issue 6645.
        */
-      tabComplete: {
-        type: Boolean,
-        value: false,
-      },
+        tabComplete: {
+          type: Boolean,
+          value: false,
+        },
 
-      value: {
-        type: String,
-        notify: true,
-      },
+        value: {
+          type: String,
+          notify: true,
+        },
 
-      /**
+        /**
        * Multi mode appends autocompleted entries to the value.
        * If false, autocompleted entries replace value.
        */
-      multi: {
-        type: Boolean,
-        value: false,
-      },
+        multi: {
+          type: Boolean,
+          value: false,
+        },
 
-      /**
+        /**
        * When true and uncommitted text is left in the autocomplete input after
        * blurring, the text will appear red.
        */
-      warnUncommitted: {
-        type: Boolean,
-        value: false,
-      },
+        warnUncommitted: {
+          type: Boolean,
+          value: false,
+        },
 
-      /**
+        /**
        * When true, querying for suggestions is not debounced w/r/t keypresses
        */
-      noDebounce: {
-        type: Boolean,
-        value: false,
-      },
+        noDebounce: {
+          type: Boolean,
+          value: false,
+        },
 
-      /** @type {?} */
-      _suggestions: {
-        type: Array,
-        value() { return []; },
-      },
+        /** @type {?} */
+        _suggestions: {
+          type: Array,
+          value() { return []; },
+        },
 
-      _suggestionEls: {
-        type: Array,
-        value() { return []; },
-      },
+        _suggestionEls: {
+          type: Array,
+          value() { return []; },
+        },
 
-      _index: Number,
-      _disableSuggestions: {
-        type: Boolean,
-        value: false,
-      },
-      _focused: {
-        type: Boolean,
-        value: false,
-      },
+        _index: Number,
+        _disableSuggestions: {
+          type: Boolean,
+          value: false,
+        },
+        _focused: {
+          type: Boolean,
+          value: false,
+        },
 
-      /** The DOM element of the selected suggestion. */
-      _selected: Object,
-    },
+        /** The DOM element of the selected suggestion. */
+        _selected: Object,
+      };
+    }
 
-    behaviors: [
-      Gerrit.FireBehavior,
-      Gerrit.KeyboardShortcutBehavior,
-    ],
-
-    observers: [
-      '_maybeOpenDropdown(_suggestions, _focused)',
-      '_updateSuggestions(text, threshold, noDebounce)',
-    ],
+    static get observers() {
+      return [
+        '_maybeOpenDropdown(_suggestions, _focused)',
+        '_updateSuggestions(text, threshold, noDebounce)',
+      ];
+    }
 
     get _nativeInput() {
       // In Polymer 2 inputElement isn't nativeInput anymore
       return this.$.input.$.nativeInput || this.$.input.inputElement;
-    },
+    }
 
     attached() {
+      super.attached();
       this.listen(document.body, 'click', '_handleBodyClick');
-    },
+    }
 
     detached() {
+      super.detached();
       this.unlisten(document.body, 'click', '_handleBodyClick');
       this.cancelDebouncer('update-suggestions');
-    },
+    }
 
     get focusStart() {
       return this.$.input;
-    },
+    }
 
     focus() {
       this._nativeInput.focus();
-    },
+    }
 
     selectAll() {
       const nativeInputElement = this._nativeInput;
       if (!this.$.input.value) { return; }
       nativeInputElement.setSelectionRange(0, this.$.input.value.length);
-    },
+    }
 
     clear() {
       this.text = '';
-    },
+    }
 
     _handleItemSelect(e) {
       // Let _handleKeydown deal with keyboard interaction.
       if (e.detail.trigger !== 'click') { return; }
       this._selected = e.detail.selected;
       this._commit();
-    },
+    }
 
     get _inputElement() {
       // Polymer2: this.$ can be undefined when this is first evaluated.
       return this.$ && this.$.input;
-    },
+    }
 
     /**
      * Set the text of the input without triggering the suggestion dropdown.
@@ -228,7 +237,7 @@
       this._disableSuggestions = true;
       this.text = text;
       this._disableSuggestions = false;
-    },
+    }
 
     _onInputFocus() {
       this._focused = true;
@@ -236,14 +245,14 @@
       this.$.input.classList.remove('warnUncommitted');
       // Needed so that --paper-input-container-input updated style is applied.
       this.updateStyles();
-    },
+    }
 
     _onInputBlur() {
       this.$.input.classList.toggle('warnUncommitted',
           this.warnUncommitted && this.text.length && !this._focused);
       // Needed so that --paper-input-container-input updated style is applied.
       this.updateStyles();
-    },
+    }
 
     _updateSuggestions(text, threshold, noDebounce) {
       // Polymer 2: check for undefined
@@ -280,18 +289,18 @@
       } else {
         this.debounce('update-suggestions', update, DEBOUNCE_WAIT_MS);
       }
-    },
+    }
 
     _maybeOpenDropdown(suggestions, focused) {
       if (suggestions.length > 0 && focused) {
         return this.$.suggestions.open();
       }
       return this.$.suggestions.close();
-    },
+    }
 
     _computeClass(borderless) {
       return borderless ? 'borderless' : '';
-    },
+    }
 
     /**
      * _handleKeydown used for key handling in the this.$.input AND all child
@@ -338,7 +347,7 @@
           this._suggestions = [];
       }
       this.fire('input-keydown', {keyCode: e.keyCode, input: this.$.input});
-    },
+    }
 
     _cancel() {
       if (this._suggestions.length) {
@@ -346,7 +355,7 @@
       } else {
         this.fire('cancel');
       }
-    },
+    }
 
     /**
      * @param {boolean=} opt_tabComplete
@@ -358,7 +367,7 @@
 
       this._selected = this.$.suggestions.getCursorTarget();
       this._commit(opt_tabComplete);
-    },
+    }
 
     _updateValue(suggestion, suggestions) {
       if (!suggestion) { return; }
@@ -372,7 +381,7 @@
       } else {
         this.value = completed;
       }
-    },
+    }
 
     _handleBodyClick(e) {
       const eventPath = Polymer.dom(e).path;
@@ -382,13 +391,13 @@
         }
       }
       this._focused = false;
-    },
+    }
 
     _handleSuggestionTap(e) {
       e.stopPropagation();
       this.$.cursor.setCursor(e.target);
       this._commit();
-    },
+    }
 
     /**
      * Commits the suggestion, optionally firing the commit event.
@@ -424,10 +433,12 @@
       }
 
       this._textChangedSinceCommit = false;
-    },
+    }
 
     _computeShowSearchIconClass(showSearchIcon) {
       return showSearchIcon ? 'showSearchIcon' : '';
-    },
-  });
+    }
+  }
+
+  customElements.define(GrAutocomplete.is, GrAutocomplete);
 })();
diff --git a/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete_test.html b/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete_test.html
index ea1fd50..68338a4 100644
--- a/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-reviewer-list</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>
diff --git a/polygerrit-ui/app/elements/shared/gr-avatar/gr-avatar.js b/polygerrit-ui/app/elements/shared/gr-avatar/gr-avatar.js
index bf56382..e57ba33 100644
--- a/polygerrit-ui/app/elements/shared/gr-avatar/gr-avatar.js
+++ b/polygerrit-ui/app/elements/shared/gr-avatar/gr-avatar.js
@@ -17,29 +17,35 @@
 (function() {
   'use strict';
 
-  Polymer({
-    is: 'gr-avatar',
+  /**
+    * @appliesMixin Gerrit.BaseUrlMixin
+    */
+  class GrAvatar extends Polymer.mixinBehaviors( [
+    Gerrit.BaseUrlBehavior,
+  ], Polymer.GestureEventListeners(
+      Polymer.LegacyElementMixin(
+          Polymer.Element))) {
+    static get is() { return 'gr-avatar'; }
 
-    properties: {
-      account: {
-        type: Object,
-        observer: '_accountChanged',
-      },
-      imageSize: {
-        type: Number,
-        value: 16,
-      },
-      _hasAvatars: {
-        type: Boolean,
-        value: false,
-      },
-    },
-
-    behaviors: [
-      Gerrit.BaseUrlBehavior,
-    ],
+    static get properties() {
+      return {
+        account: {
+          type: Object,
+          observer: '_accountChanged',
+        },
+        imageSize: {
+          type: Number,
+          value: 16,
+        },
+        _hasAvatars: {
+          type: Boolean,
+          value: false,
+        },
+      };
+    }
 
     attached() {
+      super.attached();
       Promise.all([
         this._getConfig(),
         Gerrit.awaitPluginsLoaded(),
@@ -48,15 +54,15 @@
 
         this._updateAvatarURL();
       });
-    },
+    }
 
     _getConfig() {
       return this.$.restAPI.getConfig();
-    },
+    }
 
     _accountChanged(account) {
       this._updateAvatarURL();
-    },
+    }
 
     _updateAvatarURL() {
       if (!this._hasAvatars || !this.account) {
@@ -69,12 +75,12 @@
       if (url) {
         this.style.backgroundImage = 'url("' + url + '")';
       }
-    },
+    }
 
     _getAccounts(account) {
       return account._account_id || account.email || account.username ||
           account.name;
-    },
+    }
 
     _buildAvatarURL(account) {
       if (!account) { return ''; }
@@ -87,6 +93,8 @@
       return this.getBaseUrl() + '/accounts/' +
         encodeURIComponent(this._getAccounts(account)) +
         '/avatar?s=' + this.imageSize;
-    },
-  });
+    }
+  }
+
+  customElements.define(GrAvatar.is, GrAvatar);
 })();
diff --git a/polygerrit-ui/app/elements/shared/gr-avatar/gr-avatar_test.html b/polygerrit-ui/app/elements/shared/gr-avatar/gr-avatar_test.html
index 9c2aba9..98a75dd 100644
--- a/polygerrit-ui/app/elements/shared/gr-avatar/gr-avatar_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-avatar/gr-avatar_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-avatar</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>
diff --git a/polygerrit-ui/app/elements/shared/gr-button/gr-button.js b/polygerrit-ui/app/elements/shared/gr-button/gr-button.js
index 99d4fe4..1de704a 100644
--- a/polygerrit-ui/app/elements/shared/gr-button/gr-button.js
+++ b/polygerrit-ui/app/elements/shared/gr-button/gr-button.js
@@ -17,65 +17,77 @@
 (function() {
   'use strict';
 
-  Polymer({
-    is: 'gr-button',
+  /**
+    * @appliesMixin Gerrit.KeyboardShortcutMixin
+    * @appliesMixin Gerrit.TooltipMixin
+    */
+  class GrButton extends Polymer.mixinBehaviors( [
+    Gerrit.KeyboardShortcutBehavior,
+    Gerrit.TooltipBehavior,
+  ], Polymer.GestureEventListeners(
+      Polymer.LegacyElementMixin(
+          Polymer.Element))) {
+    static get is() { return 'gr-button'; }
 
-    properties: {
-      tooltip: String,
-      downArrow: {
-        type: Boolean,
-        reflectToAttribute: true,
-      },
-      link: {
-        type: Boolean,
-        value: false,
-        reflectToAttribute: true,
-      },
-      loading: {
-        type: Boolean,
-        value: false,
-        reflectToAttribute: true,
-      },
-      disabled: {
-        type: Boolean,
-        observer: '_disabledChanged',
-        reflectToAttribute: true,
-      },
-      noUppercase: {
-        type: Boolean,
-        value: false,
-      },
-      _enabledTabindex: {
-        type: String,
-        value: '0',
-      },
-    },
+    static get properties() {
+      return {
+        tooltip: String,
+        downArrow: {
+          type: Boolean,
+          reflectToAttribute: true,
+        },
+        link: {
+          type: Boolean,
+          value: false,
+          reflectToAttribute: true,
+        },
+        loading: {
+          type: Boolean,
+          value: false,
+          reflectToAttribute: true,
+        },
+        disabled: {
+          type: Boolean,
+          observer: '_disabledChanged',
+          reflectToAttribute: true,
+        },
+        noUppercase: {
+          type: Boolean,
+          value: false,
+        },
+        _enabledTabindex: {
+          type: String,
+          value: '0',
+        },
+      };
+    }
 
-    listeners: {
-      click: '_handleAction',
-      keydown: '_handleKeydown',
-    },
+    static get observers() {
+      return [
+        '_computeDisabled(disabled, loading)',
+      ];
+    }
 
-    observers: [
-      '_computeDisabled(disabled, loading)',
-    ],
+    created() {
+      super.created();
+      this.addEventListener('click',
+          e => this._handleAction(e));
+      this.addEventListener('keydown',
+          e => this._handleKeydown(e));
+    }
 
-    behaviors: [
-      Gerrit.KeyboardShortcutBehavior,
-      Gerrit.TooltipBehavior,
-    ],
-
-    hostAttributes: {
-      role: 'button',
-      tabindex: '0',
-    },
+    ready() {
+      super.ready();
+      this._ensureAttribute('role', 'button');
+      this._ensureAttribute('tabindex', '0');
+    }
 
     _handleAction(e) {
       if (this.disabled) {
         e.preventDefault();
         e.stopImmediatePropagation();
       }
-    },
+    }
 
     _disabledChanged(disabled) {
       if (disabled) {
@@ -83,11 +95,11 @@
       }
       this.setAttribute('tabindex', disabled ? '-1' : this._enabledTabindex);
       this.updateStyles();
-    },
+    }
 
     _computeDisabled(disabled, loading) {
       return disabled || loading;
-    },
+    }
 
     _handleKeydown(e) {
       if (this.modifierPressed(e)) { return; }
@@ -98,6 +110,8 @@
         e.stopPropagation();
         this.click();
       }
-    },
-  });
+    }
+  }
+
+  customElements.define(GrButton.is, GrButton);
 })();
diff --git a/polygerrit-ui/app/elements/shared/gr-button/gr-button_test.html b/polygerrit-ui/app/elements/shared/gr-button/gr-button_test.html
index 2593ed1..d8a5443 100644
--- a/polygerrit-ui/app/elements/shared/gr-button/gr-button_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-button/gr-button_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-button</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>
diff --git a/polygerrit-ui/app/elements/shared/gr-change-star/gr-change-star.js b/polygerrit-ui/app/elements/shared/gr-change-star/gr-change-star.js
index a83bc2b..05e759f 100644
--- a/polygerrit-ui/app/elements/shared/gr-change-star/gr-change-star.js
+++ b/polygerrit-ui/app/elements/shared/gr-change-star/gr-change-star.js
@@ -17,31 +17,34 @@
 (function() {
   'use strict';
 
-  Polymer({
-    is: 'gr-change-star',
-
+  class GrChangeStar extends Polymer.GestureEventListeners(
+      Polymer.LegacyElementMixin(
+          Polymer.Element)) {
+    static get is() { return 'gr-change-star'; }
     /**
      * Fired when star state is toggled.
      *
      * @event toggle-star
      */
 
-    properties: {
+    static get properties() {
+      return {
       /** @type {?} */
-      change: {
-        type: Object,
-        notify: true,
-      },
-    },
+        change: {
+          type: Object,
+          notify: true,
+        },
+      };
+    }
 
     _computeStarClass(starred) {
       return starred ? 'active' : '';
-    },
+    }
 
     _computeStarIcon(starred) {
       // Hollow star is used to indicate inactive state.
       return `gr-icons:star${starred ? '' : '-border'}`;
-    },
+    }
 
     toggleStar() {
       const newVal = !this.change.starred;
@@ -51,6 +54,8 @@
         composed: true,
         detail: {change: this.change, starred: newVal},
       }));
-    },
-  });
+    }
+  }
+
+  customElements.define(GrChangeStar.is, GrChangeStar);
 })();
diff --git a/polygerrit-ui/app/elements/shared/gr-change-star/gr-change-star_test.html b/polygerrit-ui/app/elements/shared/gr-change-star/gr-change-star_test.html
index 7ee22a7..60c504a 100644
--- a/polygerrit-ui/app/elements/shared/gr-change-star/gr-change-star_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-change-star/gr-change-star_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-change-star</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>
diff --git a/polygerrit-ui/app/elements/shared/gr-change-status/gr-change-status.js b/polygerrit-ui/app/elements/shared/gr-change-status/gr-change-status.js
index 3afbe54..35b8ef8 100644
--- a/polygerrit-ui/app/elements/shared/gr-change-status/gr-change-status.js
+++ b/polygerrit-ui/app/elements/shared/gr-change-status/gr-change-status.js
@@ -29,38 +29,46 @@
       'It will not appear on dashboards unless you are CC\'ed or assigned, ' +
       'and email notifications will be silenced until the review is started.';
 
+  const MERGE_CONFLICT_TOOLTIP = 'This change has merge conflicts. ' +
+      'Download the patch and run "git rebase master". ' +
+      'Upload a new patchset after resolving all merge conflicts.';
+
   const PRIVATE_TOOLTIP = 'This change is only visible to its owner and ' +
       'current reviewers (or anyone with "View Private Changes" permission).';
 
-  Polymer({
-    is: 'gr-change-status',
+  class GrChangeStatus extends Polymer.GestureEventListeners(
+      Polymer.LegacyElementMixin(
+          Polymer.Element)) {
+    static get is() { return 'gr-change-status'; }
 
-    properties: {
-      flat: {
-        type: Boolean,
-        value: false,
-        reflectToAttribute: true,
-      },
-      status: {
-        type: String,
-        observer: '_updateChipDetails',
-      },
-      tooltipText: {
-        type: String,
-        value: '',
-      },
-    },
+    static get properties() {
+      return {
+        flat: {
+          type: Boolean,
+          value: false,
+          reflectToAttribute: true,
+        },
+        status: {
+          type: String,
+          observer: '_updateChipDetails',
+        },
+        tooltipText: {
+          type: String,
+          value: '',
+        },
+      };
+    }
 
     _computeStatusString(status) {
       if (status === ChangeStates.WIP && !this.flat) {
         return 'Work in Progress';
       }
       return status;
-    },
+    }
 
     _toClassName(str) {
       return str.toLowerCase().replace(/\s/g, '-');
-    },
+    }
 
     _updateChipDetails(status, previousStatus) {
       if (previousStatus) {
@@ -75,10 +83,15 @@
         case ChangeStates.PRIVATE:
           this.tooltipText = PRIVATE_TOOLTIP;
           break;
+        case ChangeStates.MERGE_CONFLICT:
+          this.tooltipText = MERGE_CONFLICT_TOOLTIP;
+          break;
         default:
           this.tooltipText = '';
           break;
       }
-    },
-  });
+    }
+  }
+
+  customElements.define(GrChangeStatus.is, GrChangeStatus);
 })();
diff --git a/polygerrit-ui/app/elements/shared/gr-change-status/gr-change-status_test.html b/polygerrit-ui/app/elements/shared/gr-change-status/gr-change-status_test.html
index 3ac4016..96a1840 100644
--- a/polygerrit-ui/app/elements/shared/gr-change-status/gr-change-status_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-change-status/gr-change-status_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-change-status</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>
@@ -35,6 +35,17 @@
 </test-fixture>
 
 <script>
+  const WIP_TOOLTIP = 'This change isn\'t ready to be reviewed or submitted. ' +
+      'It will not appear on dashboards unless you are CC\'ed or assigned, ' +
+      'and email notifications will be silenced until the review is started.';
+
+  const MERGE_CONFLICT_TOOLTIP = 'This change has merge conflicts. ' +
+    'Download the patch and run "git rebase master". ' +
+    'Upload a new patchset after resolving all merge conflicts.';
+
+  const PRIVATE_TOOLTIP = 'This change is only visible to its owner and ' +
+      'current reviewers (or anyone with "View Private Changes" permission).';
+
   suite('gr-change-status tests', () => {
     let element;
     let sandbox;
@@ -51,7 +62,7 @@
     test('WIP', () => {
       element.status = 'WIP';
       assert.equal(element.$$('.chip').innerText, 'Work in Progress');
-      assert.isDefined(element.tooltipText);
+      assert.equal(element.tooltipText, WIP_TOOLTIP);
       assert.isTrue(element.classList.contains('wip'));
     });
 
@@ -81,14 +92,14 @@
     test('merge conflict', () => {
       element.status = 'Merge Conflict';
       assert.equal(element.$$('.chip').innerText, element.status);
-      assert.equal(element.tooltipText, '');
+      assert.equal(element.tooltipText, MERGE_CONFLICT_TOOLTIP);
       assert.isTrue(element.classList.contains('merge-conflict'));
     });
 
     test('private', () => {
       element.status = 'Private';
       assert.equal(element.$$('.chip').innerText, element.status);
-      assert.isDefined(element.tooltipText);
+      assert.equal(element.tooltipText, PRIVATE_TOOLTIP);
       assert.isTrue(element.classList.contains('private'));
     });
 
diff --git a/polygerrit-ui/app/elements/shared/gr-comment-thread/gr-comment-thread.html b/polygerrit-ui/app/elements/shared/gr-comment-thread/gr-comment-thread.html
index 49b539d..d7510ec 100644
--- a/polygerrit-ui/app/elements/shared/gr-comment-thread/gr-comment-thread.html
+++ b/polygerrit-ui/app/elements/shared/gr-comment-thread/gr-comment-thread.html
@@ -87,7 +87,7 @@
           as="comment">
         <gr-comment
             comment="{{comment}}"
-            robot-button-disabled="[[_hideActions(_showActions, _lastComment)]]"
+            robot-button-disabled="[[_shouldDisableAction(_showActions, _lastComment)]]"
             change-num="[[changeNum]]"
             patch-num="[[patchNum]]"
             draft="[[_isDraft(comment)]]"
diff --git a/polygerrit-ui/app/elements/shared/gr-comment-thread/gr-comment-thread.js b/polygerrit-ui/app/elements/shared/gr-comment-thread/gr-comment-thread.js
index ff5576cb..72fc45d 100644
--- a/polygerrit-ui/app/elements/shared/gr-comment-thread/gr-comment-thread.js
+++ b/polygerrit-ui/app/elements/shared/gr-comment-thread/gr-comment-thread.js
@@ -20,9 +20,22 @@
   const UNRESOLVED_EXPAND_COUNT = 5;
   const NEWLINE_PATTERN = /\n/g;
 
-  Polymer({
-    is: 'gr-comment-thread',
-
+  /**
+    * @appliesMixin Gerrit.FireMixin
+    * @appliesMixin Gerrit.KeyboardShortcutMixin
+    * @appliesMixin Gerrit.PathListMixin
+    */
+  class GrCommentThread extends Polymer.mixinBehaviors( [
+    /**
+       * Not used in this element rather other elements tests
+       */
+    Gerrit.FireBehavior,
+    Gerrit.KeyboardShortcutBehavior,
+    Gerrit.PathListBehavior,
+  ], Polymer.GestureEventListeners(
+      Polymer.LegacyElementMixin(
+          Polymer.Element))) {
+    static get is() { return 'gr-comment-thread'; }
     /**
      * Fired when the thread should be discarded.
      *
@@ -36,122 +49,122 @@
      */
 
     /**
-      * gr-comment-thread exposes the following attributes that allow a
-      * diff widget like gr-diff to show the thread in the right location:
-      *
-      * line-num:
-      *     1-based line number or undefined if it refers to the entire file.
-      *
-      * comment-side:
-      *     "left" or "right". These indicate which of the two diffed versions
-      *     the comment relates to. In the case of unified diff, the left
-      *     version is the one whose line number column is further to the left.
-      *
-      * range:
-      *     The range of text that the comment refers to (start_line,
-      *     start_character, end_line, end_character), serialized as JSON. If
-      *     set, range's end_line will have the same value as line-num. Line
-      *     numbers are 1-based, char numbers are 0-based. The start position
-      *     (start_line, start_character) is inclusive, and the end position
-      *     (end_line, end_character) is exclusive.
-      */
-    properties: {
-      changeNum: String,
-      comments: {
-        type: Array,
-        value() { return []; },
-      },
-      /**
+              * gr-comment-thread exposes the following attributes that allow a
+              * diff widget like gr-diff to show the thread in the right location:
+              *
+              * line-num:
+              *     1-based line number or undefined if it refers to the entire file.
+              *
+              * comment-side:
+              *     "left" or "right". These indicate which of the two diffed versions
+              *     the comment relates to. In the case of unified diff, the left
+              *     version is the one whose line number column is further to the left.
+              *
+              * range:
+              *     The range of text that the comment refers to (start_line,
+              *     start_character, end_line, end_character), serialized as JSON. If
+              *     set, range's end_line will have the same value as line-num. Line
+              *     numbers are 1-based, char numbers are 0-based. The start position
+              *     (start_line, start_character) is inclusive, and the end position
+              *     (end_line, end_character) is exclusive.
+              */
+    static get properties() {
+      return {
+        changeNum: String,
+        comments: {
+          type: Array,
+          value() { return []; },
+        },
+        /**
        * @type {?{start_line: number, start_character: number, end_line: number,
        *          end_character: number}}
        */
-      range: {
-        type: Object,
-        reflectToAttribute: true,
-      },
-      keyEventTarget: {
-        type: Object,
-        value() { return document.body; },
-      },
-      commentSide: {
-        type: String,
-        reflectToAttribute: true,
-      },
-      patchNum: String,
-      path: String,
-      projectName: {
-        type: String,
-        observer: '_projectNameChanged',
-      },
-      hasDraft: {
-        type: Boolean,
-        notify: true,
-        reflectToAttribute: true,
-      },
-      isOnParent: {
-        type: Boolean,
-        value: false,
-      },
-      parentIndex: {
-        type: Number,
-        value: null,
-      },
-      rootId: {
-        type: String,
-        notify: true,
-        computed: '_computeRootId(comments.*)',
-      },
-      /**
+        range: {
+          type: Object,
+          reflectToAttribute: true,
+        },
+        keyEventTarget: {
+          type: Object,
+          value() { return document.body; },
+        },
+        commentSide: {
+          type: String,
+          reflectToAttribute: true,
+        },
+        patchNum: String,
+        path: String,
+        projectName: {
+          type: String,
+          observer: '_projectNameChanged',
+        },
+        hasDraft: {
+          type: Boolean,
+          notify: true,
+          reflectToAttribute: true,
+        },
+        isOnParent: {
+          type: Boolean,
+          value: false,
+        },
+        parentIndex: {
+          type: Number,
+          value: null,
+        },
+        rootId: {
+          type: String,
+          notify: true,
+          computed: '_computeRootId(comments.*)',
+        },
+        /**
        * If this is true, the comment thread also needs to have the change and
        * line properties property set
        */
-      showFilePath: {
-        type: Boolean,
-        value: false,
-      },
-      /** Necessary only if showFilePath is true or when used with gr-diff */
-      lineNum: {
-        type: Number,
-        reflectToAttribute: true,
-      },
-      unresolved: {
-        type: Boolean,
-        notify: true,
-        reflectToAttribute: true,
-      },
-      _showActions: Boolean,
-      _lastComment: Object,
-      _orderedComments: Array,
-      _projectConfig: Object,
-    },
+        showFilePath: {
+          type: Boolean,
+          value: false,
+        },
+        /** Necessary only if showFilePath is true or when used with gr-diff */
+        lineNum: {
+          type: Number,
+          reflectToAttribute: true,
+        },
+        unresolved: {
+          type: Boolean,
+          notify: true,
+          reflectToAttribute: true,
+        },
+        _showActions: Boolean,
+        _lastComment: Object,
+        _orderedComments: Array,
+        _projectConfig: Object,
+      };
+    }
 
-    behaviors: [
-      /**
-       * Not used in this element rather other elements tests
-       */
-      Gerrit.FireBehavior,
-      Gerrit.KeyboardShortcutBehavior,
-      Gerrit.PathListBehavior,
-    ],
+    static get observers() {
+      return [
+        '_commentsChanged(comments.*)',
+      ];
+    }
 
-    listeners: {
-      'comment-update': '_handleCommentUpdate',
-    },
+    get keyBindings() {
+      return {
+        'e shift+e': '_handleEKey',
+      };
+    }
 
-    observers: [
-      '_commentsChanged(comments.*)',
-    ],
-
-    keyBindings: {
-      'e shift+e': '_handleEKey',
-    },
+    created() {
+      super.created();
+      this.addEventListener('comment-update',
+          e => this._handleCommentUpdate(e));
+    }
 
     attached() {
+      super.attached();
       this._getLoggedIn().then(loggedIn => {
         this._showActions = loggedIn;
       });
       this._setInitialExpandedState();
-    },
+    }
 
     addOrEditDraft(opt_lineNum, opt_range) {
       const lastComment = this.comments[this.comments.length - 1] || {};
@@ -169,39 +182,39 @@
         const unresolved = lastComment ? lastComment.unresolved : undefined;
         this.addDraft(opt_lineNum, range, unresolved);
       }
-    },
+    }
 
     addDraft(opt_lineNum, opt_range, opt_unresolved) {
       const draft = this._newDraft(opt_lineNum, opt_range);
       draft.__editing = true;
       draft.unresolved = opt_unresolved === false ? opt_unresolved : true;
       this.push('comments', draft);
-    },
+    }
 
     fireRemoveSelf() {
       this.dispatchEvent(new CustomEvent('thread-discard',
           {detail: {rootId: this.rootId}, bubbles: false}));
-    },
+    }
 
     _getDiffUrlForComment(projectName, changeNum, path, patchNum) {
       return Gerrit.Nav.getUrlForDiffById(changeNum,
           projectName, path, patchNum,
           null, this.lineNum);
-    },
+    }
 
     _computeDisplayPath(path) {
       const lineString = this.lineNum ? `#${this.lineNum}` : '';
       return this.computeDisplayPath(path) + lineString;
-    },
+    }
 
     _getLoggedIn() {
       return this.$.restAPI.getLoggedIn();
-    },
+    }
 
     _commentsChanged() {
       this._orderedComments = this._sortedComments(this.comments);
       this.updateThreadProperties();
-    },
+    }
 
     updateThreadProperties() {
       if (this._orderedComments.length) {
@@ -209,16 +222,20 @@
         this.unresolved = this._lastComment.unresolved;
         this.hasDraft = this._lastComment.__draft;
       }
-    },
+    }
+
+    _shouldDisableAction(_showActions, _lastComment) {
+      return !_showActions || !_lastComment || !!_lastComment.__draft;
+    }
 
     _hideActions(_showActions, _lastComment) {
-      return !_showActions || !_lastComment || !!_lastComment.__draft ||
+      return this._shouldDisableAction(_showActions, _lastComment) ||
         !!_lastComment.robot_id;
-    },
+    }
 
     _getLastComment() {
       return this._orderedComments[this._orderedComments.length - 1] || {};
-    },
+    }
 
     _handleEKey(e) {
       if (this.shouldSuppressKeyboardShortcut(e)) { return; }
@@ -231,7 +248,7 @@
         if (this.modifierPressed(e)) { return; }
         this._expandCollapseComments(false);
       }
-    },
+    }
 
     _expandCollapseComments(actionIsCollapse) {
       const comments =
@@ -239,7 +256,7 @@
       for (const comment of comments) {
         comment.collapsed = actionIsCollapse;
       }
-    },
+    }
 
     /**
      * Sets the initial state of the comment thread.
@@ -259,7 +276,7 @@
           comment.collapsed = !isRobotComment && resolvedThread;
         }
       }
-    },
+    }
 
     _sortedComments(comments) {
       return comments.slice().sort((c1, c2) => {
@@ -275,7 +292,7 @@
         // If same date, fall back to sorting by id.
         return dateCompare ? dateCompare : c1.id.localeCompare(c2.id);
       });
-    },
+    }
 
     _createReplyComment(parent, content, opt_isEditing,
         opt_unresolved) {
@@ -309,11 +326,11 @@
           commentEl.save();
         }, 1);
       }
-    },
+    }
 
     _isDraft(comment) {
       return !!comment.__draft;
-    },
+    }
 
     /**
      * @param {boolean=} opt_quote
@@ -326,25 +343,25 @@
         quoteStr = '> ' + msg.replace(NEWLINE_PATTERN, '\n> ') + '\n\n';
       }
       this._createReplyComment(comment, quoteStr, true, comment.unresolved);
-    },
+    }
 
     _handleCommentReply(e) {
       this._processCommentReply();
-    },
+    }
 
     _handleCommentQuote(e) {
       this._processCommentReply(true);
-    },
+    }
 
     _handleCommentAck(e) {
       const comment = this._lastComment;
       this._createReplyComment(comment, 'Ack', false, false);
-    },
+    }
 
     _handleCommentDone(e) {
       const comment = this._lastComment;
       this._createReplyComment(comment, 'Done', false, false);
-    },
+    }
 
     _handleCommentFix(e) {
       const comment = e.detail.comment;
@@ -352,7 +369,7 @@
       const quoteStr = '> ' + msg.replace(NEWLINE_PATTERN, '\n> ') + '\n\n';
       const response = quoteStr + 'Please Fix';
       this._createReplyComment(comment, response, false, true);
-    },
+    }
 
     _commentElWithDraftID(id) {
       const els = Polymer.dom(this.root).querySelectorAll('gr-comment');
@@ -362,7 +379,7 @@
         }
       }
       return null;
-    },
+    }
 
     _newReply(inReplyTo, opt_lineNum, opt_message, opt_unresolved,
         opt_range) {
@@ -376,7 +393,7 @@
         d.unresolved = opt_unresolved;
       }
       return d;
-    },
+    }
 
     /**
      * @param {number=} opt_lineNum
@@ -402,12 +419,12 @@
         d.parent = this.parentIndex;
       }
       return d;
-    },
+    }
 
     _getSide(isOnParent) {
       if (isOnParent) { return 'PARENT'; }
       return 'REVISION';
-    },
+    }
 
     _computeRootId(comments) {
       // Keep the root ID even if the comment was removed, so that notification
@@ -415,7 +432,7 @@
       if (!comments.base.length) { return this.rootId; }
       const rootComment = comments.base[0];
       return rootComment.id || rootComment.__draftID;
-    },
+    }
 
     _handleCommentDiscard(e) {
       const diffCommentEl = Polymer.dom(e).rootTarget;
@@ -445,13 +462,13 @@
               changeComment.message);
         }
       }
-    },
+    }
 
     _handleCommentSavedOrDiscarded(e) {
       this.dispatchEvent(new CustomEvent('thread-changed',
           {detail: {rootId: this.rootId, path: this.path},
             bubbles: false}));
-    },
+    }
 
     _handleCommentUpdate(e) {
       const comment = e.detail.comment;
@@ -467,7 +484,7 @@
       // observers, the this.set() call above will not cause a thread update in
       // some situations.
       this.updateThreadProperties();
-    },
+    }
 
     _indexOf(comment, arr) {
       for (let i = 0; i < arr.length; i++) {
@@ -478,11 +495,11 @@
         }
       }
       return -1;
-    },
+    }
 
     _computeHostClass(unresolved) {
       return unresolved ? 'unresolved' : '';
-    },
+    }
 
     /**
      * Load the project config when a project name has been provided.
@@ -493,6 +510,8 @@
       this.$.restAPI.getProjectConfig(name).then(config => {
         this._projectConfig = config;
       });
-    },
-  });
+    }
+  }
+
+  customElements.define(GrCommentThread.is, GrCommentThread);
 })();
diff --git a/polygerrit-ui/app/elements/shared/gr-comment-thread/gr-comment-thread_test.html b/polygerrit-ui/app/elements/shared/gr-comment-thread/gr-comment-thread_test.html
index d3946e6..17e0b1f 100644
--- a/polygerrit-ui/app/elements/shared/gr-comment-thread/gr-comment-thread_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-comment-thread/gr-comment-thread_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-comment-thread</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>
@@ -152,6 +152,24 @@
       assert.isTrue(addDraftStub.called);
     });
 
+    test('_shouldDisableAction', () => {
+      let showActions = true;
+      const lastComment = {};
+      assert.equal(
+          element._shouldDisableAction(showActions, lastComment), false);
+      showActions = false;
+      assert.equal(
+          element._shouldDisableAction(showActions, lastComment), true);
+      showActions = true;
+      lastComment.__draft = true;
+      assert.equal(
+          element._shouldDisableAction(showActions, lastComment), true);
+      const robotComment = {};
+      robotComment.robot_id = true;
+      assert.equal(
+          element._shouldDisableAction(showActions, robotComment), false);
+    });
+
     test('_hideActions', () => {
       let showActions = true;
       const lastComment = {};
diff --git a/polygerrit-ui/app/elements/shared/gr-comment/gr-comment.js b/polygerrit-ui/app/elements/shared/gr-comment/gr-comment.js
index a5c0c0b..dd2855c 100644
--- a/polygerrit-ui/app/elements/shared/gr-comment/gr-comment.js
+++ b/polygerrit-ui/app/elements/shared/gr-comment/gr-comment.js
@@ -31,9 +31,17 @@
 
   const FILE = 'FILE';
 
-  Polymer({
-    is: 'gr-comment',
-
+  /**
+    * @appliesMixin Gerrit.FireMixin
+    * @appliesMixin Gerrit.KeyboardShortcutMixin
+    */
+  class GrComment extends Polymer.mixinBehaviors( [
+    Gerrit.FireBehavior,
+    Gerrit.KeyboardShortcutBehavior,
+  ], Polymer.GestureEventListeners(
+      Polymer.LegacyElementMixin(
+          Polymer.Element))) {
+    static get is() { return 'gr-comment'; }
     /**
      * Fired when the create fix comment action is triggered.
      *
@@ -64,109 +72,111 @@
      * @event comment-anchor-tap
      */
 
-    properties: {
-      changeNum: String,
-      /** @type {?} */
-      comment: {
-        type: Object,
-        notify: true,
-        observer: '_commentChanged',
-      },
-      isRobotComment: {
-        type: Boolean,
-        value: false,
-        reflectToAttribute: true,
-      },
-      disabled: {
-        type: Boolean,
-        value: false,
-        reflectToAttribute: true,
-      },
-      draft: {
-        type: Boolean,
-        value: false,
-        observer: '_draftChanged',
-      },
-      editing: {
-        type: Boolean,
-        value: false,
-        observer: '_editingChanged',
-      },
-      discarding: {
-        type: Boolean,
-        value: false,
-        reflectToAttribute: true,
-      },
-      hasChildren: Boolean,
-      patchNum: String,
-      showActions: Boolean,
-      _showHumanActions: Boolean,
-      _showRobotActions: Boolean,
-      collapsed: {
-        type: Boolean,
-        value: true,
-        observer: '_toggleCollapseClass',
-      },
-      /** @type {?} */
-      projectConfig: Object,
-      robotButtonDisabled: Boolean,
-      _isAdmin: {
-        type: Boolean,
-        value: false,
-      },
+    static get properties() {
+      return {
+        changeNum: String,
+        /** @type {?} */
+        comment: {
+          type: Object,
+          notify: true,
+          observer: '_commentChanged',
+        },
+        isRobotComment: {
+          type: Boolean,
+          value: false,
+          reflectToAttribute: true,
+        },
+        disabled: {
+          type: Boolean,
+          value: false,
+          reflectToAttribute: true,
+        },
+        draft: {
+          type: Boolean,
+          value: false,
+          observer: '_draftChanged',
+        },
+        editing: {
+          type: Boolean,
+          value: false,
+          observer: '_editingChanged',
+        },
+        discarding: {
+          type: Boolean,
+          value: false,
+          reflectToAttribute: true,
+        },
+        hasChildren: Boolean,
+        patchNum: String,
+        showActions: Boolean,
+        _showHumanActions: Boolean,
+        _showRobotActions: Boolean,
+        collapsed: {
+          type: Boolean,
+          value: true,
+          observer: '_toggleCollapseClass',
+        },
+        /** @type {?} */
+        projectConfig: Object,
+        robotButtonDisabled: Boolean,
+        _isAdmin: {
+          type: Boolean,
+          value: false,
+        },
 
-      _xhrPromise: Object, // Used for testing.
-      _messageText: {
-        type: String,
-        value: '',
-        observer: '_messageTextChanged',
-      },
-      commentSide: String,
-      side: String,
+        _xhrPromise: Object, // Used for testing.
+        _messageText: {
+          type: String,
+          value: '',
+          observer: '_messageTextChanged',
+        },
+        commentSide: String,
+        side: String,
 
-      resolved: Boolean,
+        resolved: Boolean,
 
-      _numPendingDraftRequests: {
-        type: Object,
-        value:
+        _numPendingDraftRequests: {
+          type: Object,
+          value:
             {number: 0}, // Intentional to share the object across instances.
-      },
+        },
 
-      _enableOverlay: {
-        type: Boolean,
-        value: false,
-      },
+        _enableOverlay: {
+          type: Boolean,
+          value: false,
+        },
 
-      /**
+        /**
        * Property for storing references to overlay elements. When the overlays
        * are moved to Gerrit.getRootElement() to be shown they are no-longer
        * children, so they can't be queried along the tree, so they are stored
        * here.
        */
-      _overlays: {
-        type: Object,
-        value: () => ({}),
-      },
-    },
+        _overlays: {
+          type: Object,
+          value: () => ({}),
+        },
+      };
+    }
 
-    observers: [
-      '_commentMessageChanged(comment.message)',
-      '_loadLocalDraft(changeNum, patchNum, comment)',
-      '_isRobotComment(comment)',
-      '_calculateActionstoShow(showActions, isRobotComment)',
-    ],
+    static get observers() {
+      return [
+        '_commentMessageChanged(comment.message)',
+        '_loadLocalDraft(changeNum, patchNum, comment)',
+        '_isRobotComment(comment)',
+        '_calculateActionstoShow(showActions, isRobotComment)',
+      ];
+    }
 
-    behaviors: [
-      Gerrit.FireBehavior,
-      Gerrit.KeyboardShortcutBehavior,
-    ],
-
-    keyBindings: {
-      'ctrl+enter meta+enter ctrl+s meta+s': '_handleSaveKey',
-      'esc': '_handleEsc',
-    },
+    get keyBindings() {
+      return {
+        'ctrl+enter meta+enter ctrl+s meta+s': '_handleSaveKey',
+        'esc': '_handleEsc',
+      };
+    }
 
     attached() {
+      super.attached();
       if (this.editing) {
         this.collapsed = false;
       } else if (this.comment) {
@@ -175,18 +185,19 @@
       this._getIsAdmin().then(isAdmin => {
         this._isAdmin = isAdmin;
       });
-    },
+    }
 
     detached() {
+      super.detached();
       this.cancelDebouncer('fire-update');
       if (this.textarea) {
         this.textarea.closeDropdown();
       }
-    },
+    }
 
     get textarea() {
       return this.$$('#editTextarea');
-    },
+    }
 
     get confirmDeleteOverlay() {
       if (!this._overlays.confirmDelete) {
@@ -195,7 +206,7 @@
         this._overlays.confirmDelete = this.$$('#confirmDeleteOverlay');
       }
       return this._overlays.confirmDelete;
-    },
+    }
 
     get confirmDiscardOverlay() {
       if (!this._overlays.confirmDiscard) {
@@ -204,11 +215,11 @@
         this._overlays.confirmDiscard = this.$$('#confirmDiscardOverlay');
       }
       return this._overlays.confirmDiscard;
-    },
+    }
 
     _computeShowHideIcon(collapsed) {
       return collapsed ? 'gr-icons:expand-more' : 'gr-icons:expand-less';
-    },
+    }
 
     _calculateActionstoShow(showActions, isRobotComment) {
       // Polymer 2: check for undefined
@@ -218,19 +229,19 @@
 
       this._showHumanActions = showActions && !isRobotComment;
       this._showRobotActions = showActions && isRobotComment;
-    },
+    }
 
     _isRobotComment(comment) {
       this.isRobotComment = !!comment.robot_id;
-    },
+    }
 
     isOnParent() {
       return this.side === 'PARENT';
-    },
+    }
 
     _getIsAdmin() {
       return this.$.restAPI.getIsAdmin();
-    },
+    }
 
     /**
      * @param {*=} opt_comment
@@ -273,7 +284,7 @@
       });
 
       return this._xhrPromise;
-    },
+    }
 
     _eraseDraftComment() {
       // Prevents a race condition in which removing the draft comment occurs
@@ -287,7 +298,7 @@
         line: this.comment.line,
         range: this.comment.range,
       });
-    },
+    }
 
     _commentChanged(comment) {
       this.editing = !!comment.__editing;
@@ -295,7 +306,7 @@
       if (this.editing) { // It's a new draft/reply, notify.
         this._fireUpdate();
       }
-    },
+    }
 
     /**
      * @param {!Object=} opt_mixin
@@ -307,21 +318,21 @@
         comment: this.comment,
         patchNum: this.patchNum,
       });
-    },
+    }
 
     _fireSave() {
       this.fire('comment-save', this._getEventPayload());
-    },
+    }
 
     _fireUpdate() {
       this.debounce('fire-update', () => {
         this.fire('comment-update', this._getEventPayload());
       });
-    },
+    }
 
     _draftChanged(draft) {
       this.$.container.classList.toggle('draft', draft);
-    },
+    }
 
     _editingChanged(editing, previousValue) {
       // Polymer 2: observer fires when at least one property is defined.
@@ -346,11 +357,11 @@
           this.textarea.putCursorAtEnd();
         }, 1);
       }
-    },
+    }
 
     _computeDeleteButtonClass(isAdmin, draft) {
       return isAdmin && !draft ? 'showDeleteButtons' : '';
-    },
+    }
 
     _computeSaveDisabled(draft, comment, resolved) {
       // If resolved state has changed and a msg exists, save should be enabled.
@@ -358,7 +369,7 @@
         return false;
       }
       return !draft || draft.trim() === '';
-    },
+    }
 
     _handleSaveKey(e) {
       if (!this._computeSaveDisabled(this._messageText, this.comment,
@@ -366,18 +377,18 @@
         e.preventDefault();
         this._handleSave(e);
       }
-    },
+    }
 
     _handleEsc(e) {
       if (!this._messageText.length) {
         e.preventDefault();
         this._handleCancel(e);
       }
-    },
+    }
 
     _handleToggleCollapsed() {
       this.collapsed = !this.collapsed;
-    },
+    }
 
     _toggleCollapseClass(collapsed) {
       if (collapsed) {
@@ -385,11 +396,11 @@
       } else {
         this.$.container.classList.remove('collapsed');
       }
-    },
+    }
 
     _commentMessageChanged(message) {
       this._messageText = message || '';
-    },
+    }
 
     _messageTextChanged(newValue, oldValue) {
       if (!this.comment || (this.comment && this.comment.id)) {
@@ -414,7 +425,7 @@
           this.$.storage.setDraftComment(commentLocation, message);
         }
       }, STORAGE_DEBOUNCE_INTERVAL);
-    },
+    }
 
     _handleAnchorClick(e) {
       e.preventDefault();
@@ -429,14 +440,14 @@
           side: this.side,
         },
       }));
-    },
+    }
 
     _handleEdit(e) {
       e.preventDefault();
       this._messageText = this.comment.message;
       this.editing = true;
       this.$.reporting.recordDraftInteraction();
-    },
+    }
 
     _handleSave(e) {
       e.preventDefault();
@@ -450,7 +461,7 @@
       const timer = this.$.reporting.getTimer(timingLabel);
       this.set('comment.__editing', false);
       return this.save().then(() => { timer.end(); });
-    },
+    }
 
     _handleCancel(e) {
       e.preventDefault();
@@ -463,12 +474,12 @@
       }
       this._messageText = this.comment.message;
       this.editing = false;
-    },
+    }
 
     _fireDiscard() {
       this.cancelDebouncer('fire-update');
       this.fire('comment-discard', this._getEventPayload());
-    },
+    }
 
     _handleFix() {
       this.dispatchEvent(new CustomEvent('create-fix-comment', {
@@ -476,7 +487,7 @@
         composed: true,
         detail: this._getEventPayload(),
       }));
-    },
+    }
 
     _handleDiscard(e) {
       e.preventDefault();
@@ -491,14 +502,14 @@
         this.confirmDiscardOverlay.querySelector('#confirmDiscardDialog')
             .resetFocus();
       });
-    },
+    }
 
     _handleConfirmDiscard(e) {
       e.preventDefault();
       const timer = this.$.reporting.getTimer(REPORT_DISCARD_DRAFT);
       this._closeConfirmDiscardOverlay();
       return this._discardDraft().then(() => { timer.end(); });
-    },
+    }
 
     _discardDraft() {
       if (!this.comment.__draft) {
@@ -529,11 +540,11 @@
       });
 
       return this._xhrPromise;
-    },
+    }
 
     _closeConfirmDiscardOverlay() {
       this._closeOverlay(this.confirmDiscardOverlay);
-    },
+    }
 
     _getSavingMessage(numPending) {
       if (numPending === 0) {
@@ -544,17 +555,17 @@
         numPending,
         numPending === 1 ? DRAFT_SINGULAR : DRAFT_PLURAL,
       ].join(' ');
-    },
+    }
 
     _showStartRequest() {
       const numPending = ++this._numPendingDraftRequests.number;
       this._updateRequestToast(numPending);
-    },
+    }
 
     _showEndRequest() {
       const numPending = --this._numPendingDraftRequests.number;
       this._updateRequestToast(numPending);
-    },
+    }
 
     _handleFailedDraftRequest() {
       this._numPendingDraftRequests.number--;
@@ -562,7 +573,7 @@
       // Cancel the debouncer so that error toasts from the error-manager will
       // not be overridden.
       this.cancelDebouncer('draft-toast');
-    },
+    }
 
     _updateRequestToast(numPending) {
       const message = this._getSavingMessage(numPending);
@@ -573,7 +584,7 @@
         document.body.dispatchEvent(new CustomEvent(
             'show-alert', {detail: {message}, bubbles: true, composed: true}));
       }, TOAST_DEBOUNCE_INTERVAL);
-    },
+    }
 
     _saveDraft(draft) {
       this._showStartRequest();
@@ -586,7 +597,7 @@
             }
             return result;
           });
-    },
+    }
 
     _deleteDraft(draft) {
       this._showStartRequest();
@@ -599,11 +610,11 @@
         }
         return result;
       });
-    },
+    }
 
     _getPatchNum() {
       return this.isOnParent() ? 'PARENT' : this.patchNum;
-    },
+    }
 
     _loadLocalDraft(changeNum, patchNum, comment) {
       // Polymer 2: check for undefined
@@ -632,7 +643,7 @@
       if (draft) {
         this.set('comment.message', draft.message);
       }
-    },
+    }
 
     _handleToggleResolved() {
       this.$.reporting.recordDraftInteraction();
@@ -646,25 +657,25 @@
         // Save the resolved state immediately.
         this.save(payload.comment);
       }
-    },
+    }
 
     _handleCommentDelete() {
       this._openOverlay(this.confirmDeleteOverlay);
-    },
+    }
 
     _handleCancelDeleteComment() {
       this._closeOverlay(this.confirmDeleteOverlay);
-    },
+    }
 
     _openOverlay(overlay) {
       Polymer.dom(Gerrit.getRootElement()).appendChild(overlay);
       return overlay.open();
-    },
+    }
 
     _closeOverlay(overlay) {
       Polymer.dom(Gerrit.getRootElement()).removeChild(overlay);
       overlay.close();
-    },
+    }
 
     _handleConfirmDeleteComment() {
       const dialog =
@@ -675,6 +686,8 @@
             this._handleCancelDeleteComment();
             this.comment = newComment;
           });
-    },
-  });
+    }
+  }
+
+  customElements.define(GrComment.is, GrComment);
 })();
diff --git a/polygerrit-ui/app/elements/shared/gr-comment/gr-comment_test.html b/polygerrit-ui/app/elements/shared/gr-comment/gr-comment_test.html
index 560f9fa..de75b40 100644
--- a/polygerrit-ui/app/elements/shared/gr-comment/gr-comment_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-comment/gr-comment_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-comment</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>
diff --git a/polygerrit-ui/app/elements/shared/gr-confirm-delete-comment-dialog/gr-confirm-delete-comment-dialog.js b/polygerrit-ui/app/elements/shared/gr-confirm-delete-comment-dialog/gr-confirm-delete-comment-dialog.js
index c2075f0..79cd251 100644
--- a/polygerrit-ui/app/elements/shared/gr-confirm-delete-comment-dialog/gr-confirm-delete-comment-dialog.js
+++ b/polygerrit-ui/app/elements/shared/gr-confirm-delete-comment-dialog/gr-confirm-delete-comment-dialog.js
@@ -17,9 +17,15 @@
 (function() {
   'use strict';
 
-  Polymer({
-    is: 'gr-confirm-delete-comment-dialog',
-
+  /**
+    * @appliesMixin Gerrit.FireMixin
+    */
+  class GrConfirmDeleteCommentDialog extends Polymer.mixinBehaviors( [
+    Gerrit.FireBehavior,
+  ], Polymer.GestureEventListeners(
+      Polymer.LegacyElementMixin(
+          Polymer.Element))) {
+    static get is() { return 'gr-confirm-delete-comment-dialog'; }
     /**
      * Fired when the confirm button is pressed.
      *
@@ -32,28 +38,29 @@
      * @event cancel
      */
 
-    properties: {
-      message: String,
-    },
-
-    behaviors: [
-      Gerrit.FireBehavior,
-    ],
+    static get properties() {
+      return {
+        message: String,
+      };
+    }
 
     resetFocus() {
       this.$.messageInput.textarea.focus();
-    },
+    }
 
     _handleConfirmTap(e) {
       e.preventDefault();
       e.stopPropagation();
       this.fire('confirm', {reason: this.message}, {bubbles: false});
-    },
+    }
 
     _handleCancelTap(e) {
       e.preventDefault();
       e.stopPropagation();
       this.fire('cancel', null, {bubbles: false});
-    },
-  });
+    }
+  }
+
+  customElements.define(GrConfirmDeleteCommentDialog.is,
+      GrConfirmDeleteCommentDialog);
 })();
diff --git a/polygerrit-ui/app/elements/shared/gr-copy-clipboard/gr-copy-clipboard.html b/polygerrit-ui/app/elements/shared/gr-copy-clipboard/gr-copy-clipboard.html
index 0d8d163..f58db39 100644
--- a/polygerrit-ui/app/elements/shared/gr-copy-clipboard/gr-copy-clipboard.html
+++ b/polygerrit-ui/app/elements/shared/gr-copy-clipboard/gr-copy-clipboard.html
@@ -47,6 +47,12 @@
         height: 1.2em;
         width: 1.2em;
       }
+      gr-button {
+        --gr-button: {
+          padding: 1px 4px;
+        }
+      }
+
     </style>
     <div class="text">
       <iron-input
diff --git a/polygerrit-ui/app/elements/shared/gr-copy-clipboard/gr-copy-clipboard.js b/polygerrit-ui/app/elements/shared/gr-copy-clipboard/gr-copy-clipboard.js
index 3e87202..6c062f6 100644
--- a/polygerrit-ui/app/elements/shared/gr-copy-clipboard/gr-copy-clipboard.js
+++ b/polygerrit-ui/app/elements/shared/gr-copy-clipboard/gr-copy-clipboard.js
@@ -19,34 +19,38 @@
 
   const COPY_TIMEOUT_MS = 1000;
 
-  Polymer({
-    is: 'gr-copy-clipboard',
+  class GrCopyClipboard extends Polymer.GestureEventListeners(
+      Polymer.LegacyElementMixin(
+          Polymer.Element)) {
+    static get is() { return 'gr-copy-clipboard'; }
 
-    properties: {
-      text: String,
-      buttonTitle: String,
-      hasTooltip: {
-        type: Boolean,
-        value: false,
-      },
-      hideInput: {
-        type: Boolean,
-        value: false,
-      },
-    },
+    static get properties() {
+      return {
+        text: String,
+        buttonTitle: String,
+        hasTooltip: {
+          type: Boolean,
+          value: false,
+        },
+        hideInput: {
+          type: Boolean,
+          value: false,
+        },
+      };
+    }
 
     focusOnCopy() {
       this.$.button.focus();
-    },
+    }
 
     _computeInputClass(hideInput) {
       return hideInput ? 'hideInput' : '';
-    },
+    }
 
     _handleInputClick(e) {
       e.preventDefault();
       Polymer.dom(e).rootTarget.select();
-    },
+    }
 
     _copyToClipboard() {
       if (this.hideInput) {
@@ -62,6 +66,8 @@
       this.async(
           () => this.$.icon.icon = 'gr-icons:content-copy',
           COPY_TIMEOUT_MS);
-    },
-  });
+    }
+  }
+
+  customElements.define(GrCopyClipboard.is, GrCopyClipboard);
 })();
diff --git a/polygerrit-ui/app/elements/shared/gr-copy-clipboard/gr-copy-clipboard_test.html b/polygerrit-ui/app/elements/shared/gr-copy-clipboard/gr-copy-clipboard_test.html
index 9cec20e..14490f0 100644
--- a/polygerrit-ui/app/elements/shared/gr-copy-clipboard/gr-copy-clipboard_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-copy-clipboard/gr-copy-clipboard_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-copy-clipboard</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>
diff --git a/polygerrit-ui/app/elements/shared/gr-count-string-formatter/gr-count-string-formatter_test.html b/polygerrit-ui/app/elements/shared/gr-count-string-formatter/gr-count-string-formatter_test.html
index d061ac2..5022766 100644
--- a/polygerrit-ui/app/elements/shared/gr-count-string-formatter/gr-count-string-formatter_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-count-string-formatter/gr-count-string-formatter_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-count-string-formatter</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>
diff --git a/polygerrit-ui/app/elements/shared/gr-cursor-manager/gr-cursor-manager.js b/polygerrit-ui/app/elements/shared/gr-cursor-manager/gr-cursor-manager.js
index 4e9ff76..4232ef9 100644
--- a/polygerrit-ui/app/elements/shared/gr-cursor-manager/gr-cursor-manager.js
+++ b/polygerrit-ui/app/elements/shared/gr-cursor-manager/gr-cursor-manager.js
@@ -22,72 +22,77 @@
     KEEP_VISIBLE: 'keep-visible',
   };
 
-  Polymer({
-    is: 'gr-cursor-manager',
+  class GrCursorManager extends Polymer.GestureEventListeners(
+      Polymer.LegacyElementMixin(
+          Polymer.Element)) {
+    static get is() { return 'gr-cursor-manager'; }
 
-    properties: {
-      stops: {
-        type: Array,
-        value() {
-          return [];
+    static get properties() {
+      return {
+        stops: {
+          type: Array,
+          value() {
+            return [];
+          },
+          observer: '_updateIndex',
         },
-        observer: '_updateIndex',
-      },
-      /**
+        /**
        * @type (?Object)
        */
-      target: {
-        type: Object,
-        notify: true,
-        observer: '_scrollToTarget',
-      },
-      /**
+        target: {
+          type: Object,
+          notify: true,
+          observer: '_scrollToTarget',
+        },
+        /**
        * The height of content intended to be included with the target.
        * @type (?number)
        */
-      _targetHeight: Number,
+        _targetHeight: Number,
 
-      /**
+        /**
        * The index of the current target (if any). -1 otherwise.
        */
-      index: {
-        type: Number,
-        value: -1,
-        notify: true,
-      },
+        index: {
+          type: Number,
+          value: -1,
+          notify: true,
+        },
 
-      /**
+        /**
        * The class to apply to the current target. Use null for no class.
        */
-      cursorTargetClass: {
-        type: String,
-        value: null,
-      },
+        cursorTargetClass: {
+          type: String,
+          value: null,
+        },
 
-      /**
+        /**
        * The scroll behavior for the cursor. Values are 'never' and
        * 'keep-visible'. 'keep-visible' will only scroll if the cursor is beyond
        * the viewport.
        * TODO (beckysiegel) figure out why it can be undefined
        * @type (string|undefined)
        */
-      scrollBehavior: {
-        type: String,
-        value: ScrollBehavior.NEVER,
-      },
+        scrollBehavior: {
+          type: String,
+          value: ScrollBehavior.NEVER,
+        },
 
-      /**
+        /**
        * When true, will call element.focus() during scrolling.
        */
-      focusOnMove: {
-        type: Boolean,
-        value: false,
-      },
-    },
+        focusOnMove: {
+          type: Boolean,
+          value: false,
+        },
+      };
+    }
 
     detached() {
+      super.detached();
       this.unsetCursor();
-    },
+    }
 
     /**
      * Move the cursor forward. Clipped to the ends of the stop list.
@@ -104,11 +109,11 @@
 
     next(opt_condition, opt_getTargetHeight, opt_clipToTop) {
       this._moveCursor(1, opt_condition, opt_getTargetHeight, opt_clipToTop);
-    },
+    }
 
     previous(opt_condition) {
       this._moveCursor(-1, opt_condition);
-    },
+    }
 
     /**
      * Set the cursor to an arbitrary element.
@@ -129,32 +134,32 @@
       this._decorateTarget();
 
       if (opt_noScroll) { this.scrollBehavior = behavior; }
-    },
+    }
 
     unsetCursor() {
       this._unDecorateTarget();
       this.index = -1;
       this.target = null;
       this._targetHeight = null;
-    },
+    }
 
     isAtStart() {
       return this.index === 0;
-    },
+    }
 
     isAtEnd() {
       return this.index === this.stops.length - 1;
-    },
+    }
 
     moveToStart() {
       if (this.stops.length) {
         this.setCursor(this.stops[0]);
       }
-    },
+    }
 
     setCursorAtIndex(index, opt_noScroll) {
       this.setCursor(this.stops[index], opt_noScroll);
-    },
+    }
 
     /**
      * Move the cursor forward or backward by delta. Clipped to the beginning or
@@ -199,19 +204,19 @@
       if (this.focusOnMove) { this.target.focus(); }
 
       this._decorateTarget();
-    },
+    }
 
     _decorateTarget() {
       if (this.target && this.cursorTargetClass) {
         this.target.classList.add(this.cursorTargetClass);
       }
-    },
+    }
 
     _unDecorateTarget() {
       if (this.target && this.cursorTargetClass) {
         this.target.classList.remove(this.cursorTargetClass);
       }
-    },
+    }
 
     /**
      * Get the next stop index indicated by the delta direction.
@@ -247,7 +252,7 @@
       }
 
       return newIndex;
-    },
+    }
 
     _updateIndex() {
       if (!this.target) {
@@ -261,7 +266,7 @@
       } else {
         this.index = newIndex;
       }
-    },
+    }
 
     /**
      * Calculate where the element is relative to the window.
@@ -276,7 +281,7 @@
         top += offsetParent.offsetTop;
       }
       return top;
-    },
+    }
 
     /**
      * @return {boolean}
@@ -286,12 +291,12 @@
       return this.scrollBehavior === ScrollBehavior.KEEP_VISIBLE &&
           top > dims.pageYOffset &&
           top < dims.pageYOffset + dims.innerHeight;
-    },
+    }
 
     _calculateScrollToValue(top, target) {
       const dims = this._getWindowDims();
       return top - (dims.innerHeight / 3) + (target.offsetHeight / 2);
-    },
+    }
 
     _scrollToTarget() {
       if (!this.target || this.scrollBehavior === ScrollBehavior.NEVER) {
@@ -319,7 +324,7 @@
       // element appears to be below the center of the window even when it
       // isn't.
       window.scrollTo(dims.scrollX, scrollToValue);
-    },
+    }
 
     _getWindowDims() {
       return {
@@ -328,6 +333,8 @@
         innerHeight: window.innerHeight,
         pageYOffset: window.pageYOffset,
       };
-    },
-  });
+    }
+  }
+
+  customElements.define(GrCursorManager.is, GrCursorManager);
 })();
diff --git a/polygerrit-ui/app/elements/shared/gr-cursor-manager/gr-cursor-manager_test.html b/polygerrit-ui/app/elements/shared/gr-cursor-manager/gr-cursor-manager_test.html
index b51e44c..103b41a 100644
--- a/polygerrit-ui/app/elements/shared/gr-cursor-manager/gr-cursor-manager_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-cursor-manager/gr-cursor-manager_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-cursor-manager</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>
diff --git a/polygerrit-ui/app/elements/shared/gr-date-formatter/gr-date-formatter.js b/polygerrit-ui/app/elements/shared/gr-date-formatter/gr-date-formatter.js
index 5b1ef7f..f79c1b6 100644
--- a/polygerrit-ui/app/elements/shared/gr-date-formatter/gr-date-formatter.js
+++ b/polygerrit-ui/app/elements/shared/gr-date-formatter/gr-date-formatter.js
@@ -31,50 +31,56 @@
     MONTH_DAY_YEAR: 'MMM DD, YYYY', // Aug 29, 1997
   };
 
-  Polymer({
-    is: 'gr-date-formatter',
+  /**
+    * @appliesMixin Gerrit.TooltipMixin
+    */
+  class GrDateFormatter extends Polymer.mixinBehaviors( [
+    Gerrit.TooltipBehavior,
+  ], Polymer.GestureEventListeners(
+      Polymer.LegacyElementMixin(
+          Polymer.Element))) {
+    static get is() { return 'gr-date-formatter'; }
 
-    properties: {
-      dateStr: {
-        type: String,
-        value: null,
-        notify: true,
-      },
-      showDateAndTime: {
-        type: Boolean,
-        value: false,
-      },
+    static get properties() {
+      return {
+        dateStr: {
+          type: String,
+          value: null,
+          notify: true,
+        },
+        showDateAndTime: {
+          type: Boolean,
+          value: false,
+        },
 
-      /**
+        /**
        * When true, the detailed date appears in a GR-TOOLTIP rather than in the
        * native browser tooltip.
        */
-      hasTooltip: Boolean,
+        hasTooltip: Boolean,
 
-      /**
+        /**
        * The title to be used as the native tooltip or by the tooltip behavior.
        */
-      title: {
-        type: String,
-        reflectToAttribute: true,
-        computed: '_computeFullDateStr(dateStr, _timeFormat)',
-      },
+        title: {
+          type: String,
+          reflectToAttribute: true,
+          computed: '_computeFullDateStr(dateStr, _timeFormat)',
+        },
 
-      _timeFormat: String, // No default value to prevent flickering.
-      _relative: Boolean, // No default value to prevent flickering.
-    },
-
-    behaviors: [
-      Gerrit.TooltipBehavior,
-    ],
+        _timeFormat: String, // No default value to prevent flickering.
+        _relative: Boolean, // No default value to prevent flickering.
+      };
+    }
 
     attached() {
+      super.attached();
       this._loadPreferences();
-    },
+    }
 
     _getUtcOffsetString() {
       return ' UTC' + moment().format('Z');
-    },
+    }
 
     _loadPreferences() {
       return this._getLoggedIn().then(loggedIn => {
@@ -88,7 +94,7 @@
           this._loadRelative(),
         ]);
       });
-    },
+    }
 
     _loadTimeFormat() {
       return this._getPreferences().then(preferences => {
@@ -104,22 +110,22 @@
             throw Error('Invalid time format: ' + timeFormat);
         }
       });
-    },
+    }
 
     _loadRelative() {
       return this._getPreferences().then(prefs => {
         // prefs.relative_date_in_change_table is not set when false.
         this._relative = !!(prefs && prefs.relative_date_in_change_table);
       });
-    },
+    }
 
     _getLoggedIn() {
       return this.$.restAPI.getLoggedIn();
-    },
+    }
 
     _getPreferences() {
       return this.$.restAPI.getPreferences();
-    },
+    }
 
     /**
      * Return true if date is within 24 hours and on the same day.
@@ -127,7 +133,7 @@
     _isWithinDay(now, date) {
       const diff = -date.diff(now);
       return diff < Duration.DAY && date.day() === now.getDay();
-    },
+    }
 
     /**
      * Returns true if date is from one to six months.
@@ -136,7 +142,7 @@
       const diff = -date.diff(now);
       return (date.day() !== now.getDay() || diff >= Duration.DAY) &&
           diff < 180 * Duration.DAY;
-    },
+    }
 
     _computeDateStr(dateStr, timeFormat, relative, showDateAndTime) {
       if (!dateStr) { return ''; }
@@ -163,13 +169,13 @@
         }
       }
       return date.format(format);
-    },
+    }
 
     _timeToSecondsFormat(timeFormat) {
       return timeFormat === TimeFormats.TIME_12 ?
         TimeFormats.TIME_12_WITH_SEC :
         TimeFormats.TIME_24_WITH_SEC;
-    },
+    }
 
     _computeFullDateStr(dateStr, timeFormat) {
       // Polymer 2: check for undefined
@@ -186,6 +192,8 @@
       let format = TimeFormats.MONTH_DAY_YEAR + ', ';
       format += this._timeToSecondsFormat(timeFormat);
       return date.format(format) + this._getUtcOffsetString();
-    },
-  });
+    }
+  }
+
+  customElements.define(GrDateFormatter.is, GrDateFormatter);
 })();
diff --git a/polygerrit-ui/app/elements/shared/gr-date-formatter/gr-date-formatter_test.html b/polygerrit-ui/app/elements/shared/gr-date-formatter/gr-date-formatter_test.html
index 99af4e6..fffd38e 100644
--- a/polygerrit-ui/app/elements/shared/gr-date-formatter/gr-date-formatter_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-date-formatter/gr-date-formatter_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-date-formatter</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>
diff --git a/polygerrit-ui/app/elements/shared/gr-dialog/gr-dialog.js b/polygerrit-ui/app/elements/shared/gr-dialog/gr-dialog.js
index 68dc537..b84434e 100644
--- a/polygerrit-ui/app/elements/shared/gr-dialog/gr-dialog.js
+++ b/polygerrit-ui/app/elements/shared/gr-dialog/gr-dialog.js
@@ -17,9 +17,15 @@
 (function() {
   'use strict';
 
-  Polymer({
-    is: 'gr-dialog',
-
+  /**
+    * @appliesMixin Gerrit.FireMixin
+    */
+  class GrDialog extends Polymer.mixinBehaviors( [
+    Gerrit.FireBehavior,
+  ], Polymer.GestureEventListeners(
+      Polymer.LegacyElementMixin(
+          Polymer.Element))) {
+    static get is() { return 'gr-dialog'; }
     /**
      * Fired when the confirm button is pressed.
      *
@@ -32,33 +38,32 @@
      * @event cancel
      */
 
-    properties: {
-      confirmLabel: {
-        type: String,
-        value: 'Confirm',
-      },
-      // Supplying an empty cancel label will hide the button completely.
-      cancelLabel: {
-        type: String,
-        value: 'Cancel',
-      },
-      disabled: {
-        type: Boolean,
-        value: false,
-      },
-      confirmOnEnter: {
-        type: Boolean,
-        value: false,
-      },
-    },
+    static get properties() {
+      return {
+        confirmLabel: {
+          type: String,
+          value: 'Confirm',
+        },
+        // Supplying an empty cancel label will hide the button completely.
+        cancelLabel: {
+          type: String,
+          value: 'Cancel',
+        },
+        disabled: {
+          type: Boolean,
+          value: false,
+        },
+        confirmOnEnter: {
+          type: Boolean,
+          value: false,
+        },
+      };
+    }
 
-    behaviors: [
-      Gerrit.FireBehavior,
-    ],
-
-    hostAttributes: {
-      role: 'dialog',
-    },
+    ready() {
+      super.ready();
+      this._ensureAttribute('role', 'dialog');
+    }
 
     _handleConfirm(e) {
       if (this.disabled) { return; }
@@ -66,24 +71,26 @@
       e.preventDefault();
       e.stopPropagation();
       this.fire('confirm', null, {bubbles: false});
-    },
+    }
 
     _handleCancelTap(e) {
       e.preventDefault();
       e.stopPropagation();
       this.fire('cancel', null, {bubbles: false});
-    },
+    }
 
     _handleKeydown(e) {
       if (this.confirmOnEnter && e.keyCode === 13) { this._handleConfirm(e); }
-    },
+    }
 
     resetFocus() {
       this.$.confirm.focus();
-    },
+    }
 
     _computeCancelClass(cancelLabel) {
       return cancelLabel.length ? '' : 'hidden';
-    },
-  });
+    }
+  }
+
+  customElements.define(GrDialog.is, GrDialog);
 })();
diff --git a/polygerrit-ui/app/elements/shared/gr-dialog/gr-dialog_test.html b/polygerrit-ui/app/elements/shared/gr-dialog/gr-dialog_test.html
index 1456e77..ced925e 100644
--- a/polygerrit-ui/app/elements/shared/gr-dialog/gr-dialog_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-dialog/gr-dialog_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-dialog</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>
diff --git a/polygerrit-ui/app/elements/shared/gr-diff-preferences/gr-diff-preferences.js b/polygerrit-ui/app/elements/shared/gr-diff-preferences/gr-diff-preferences.js
index 36fdf5b..6e34bbc 100644
--- a/polygerrit-ui/app/elements/shared/gr-diff-preferences/gr-diff-preferences.js
+++ b/polygerrit-ui/app/elements/shared/gr-diff-preferences/gr-diff-preferences.js
@@ -17,62 +17,68 @@
 (function() {
   'use strict';
 
-  Polymer({
-    is: 'gr-diff-preferences',
+  class GrDiffPreferences extends Polymer.GestureEventListeners(
+      Polymer.LegacyElementMixin(
+          Polymer.Element)) {
+    static get is() { return 'gr-diff-preferences'; }
 
-    properties: {
-      hasUnsavedChanges: {
-        type: Boolean,
-        notify: true,
-        value: false,
-      },
+    static get properties() {
+      return {
+        hasUnsavedChanges: {
+          type: Boolean,
+          notify: true,
+          value: false,
+        },
 
-      /** @type {?} */
-      diffPrefs: Object,
-    },
+        /** @type {?} */
+        diffPrefs: Object,
+      };
+    }
 
     loadData() {
       return this.$.restAPI.getDiffPreferences().then(prefs => {
         this.diffPrefs = prefs;
       });
-    },
+    }
 
     _handleDiffPrefsChanged() {
       this.hasUnsavedChanges = true;
-    },
+    }
 
     _handleLineWrappingTap() {
       this.set('diffPrefs.line_wrapping', this.$.lineWrappingInput.checked);
       this._handleDiffPrefsChanged();
-    },
+    }
 
     _handleShowTabsTap() {
       this.set('diffPrefs.show_tabs', this.$.showTabsInput.checked);
       this._handleDiffPrefsChanged();
-    },
+    }
 
     _handleShowTrailingWhitespaceTap() {
       this.set('diffPrefs.show_whitespace_errors',
           this.$.showTrailingWhitespaceInput.checked);
       this._handleDiffPrefsChanged();
-    },
+    }
 
     _handleSyntaxHighlightTap() {
       this.set('diffPrefs.syntax_highlighting',
           this.$.syntaxHighlightInput.checked);
       this._handleDiffPrefsChanged();
-    },
+    }
 
     _handleAutomaticReviewTap() {
       this.set('diffPrefs.manual_review',
           !this.$.automaticReviewInput.checked);
       this._handleDiffPrefsChanged();
-    },
+    }
 
     save() {
       return this.$.restAPI.saveDiffPreferences(this.diffPrefs).then(res => {
         this.hasUnsavedChanges = false;
       });
-    },
-  });
+    }
+  }
+
+  customElements.define(GrDiffPreferences.is, GrDiffPreferences);
 })();
diff --git a/polygerrit-ui/app/elements/shared/gr-diff-preferences/gr-diff-preferences_test.html b/polygerrit-ui/app/elements/shared/gr-diff-preferences/gr-diff-preferences_test.html
index 4d2a1a4..fa1ade5 100644
--- a/polygerrit-ui/app/elements/shared/gr-diff-preferences/gr-diff-preferences_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-diff-preferences/gr-diff-preferences_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-diff-preferences</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>
diff --git a/polygerrit-ui/app/elements/shared/gr-download-commands/gr-download-commands.js b/polygerrit-ui/app/elements/shared/gr-download-commands/gr-download-commands.js
index 121aa35..8ee4820 100644
--- a/polygerrit-ui/app/elements/shared/gr-download-commands/gr-download-commands.js
+++ b/polygerrit-ui/app/elements/shared/gr-download-commands/gr-download-commands.js
@@ -17,40 +17,46 @@
 (function() {
   'use strict';
 
-  Polymer({
-    is: 'gr-download-commands',
+  /**
+    * @appliesMixin Gerrit.RESTClientMixin
+    */
+  class GrDownloadCommands extends Polymer.mixinBehaviors( [
+    Gerrit.RESTClientBehavior,
+  ], Polymer.GestureEventListeners(
+      Polymer.LegacyElementMixin(
+          Polymer.Element))) {
+    static get is() { return 'gr-download-commands'; }
 
-    properties: {
-      commands: Array,
-      _loggedIn: {
-        type: Boolean,
-        value: false,
-        observer: '_loggedInChanged',
-      },
-      schemes: Array,
-      selectedScheme: {
-        type: String,
-        notify: true,
-      },
-    },
-
-    behaviors: [
-      Gerrit.RESTClientBehavior,
-    ],
+    static get properties() {
+      return {
+        commands: Array,
+        _loggedIn: {
+          type: Boolean,
+          value: false,
+          observer: '_loggedInChanged',
+        },
+        schemes: Array,
+        selectedScheme: {
+          type: String,
+          notify: true,
+        },
+      };
+    }
 
     attached() {
+      super.attached();
       this._getLoggedIn().then(loggedIn => {
         this._loggedIn = loggedIn;
       });
-    },
+    }
 
     focusOnCopy() {
       this.$$('gr-shell-command').focusOnCopy();
-    },
+    }
 
     _getLoggedIn() {
       return this.$.restAPI.getLoggedIn();
-    },
+    }
 
     _loggedInChanged(loggedIn) {
       if (!loggedIn) { return; }
@@ -60,7 +66,7 @@
           this.selectedScheme = prefs.download_scheme.toLowerCase();
         }
       });
-    },
+    }
 
     _handleTabChange(e) {
       const scheme = this.schemes[e.detail.value];
@@ -71,15 +77,17 @@
               {download_scheme: this.selectedScheme});
         }
       }
-    },
+    }
 
     _computeSelected(schemes, selectedScheme) {
       return (schemes.findIndex(scheme => scheme === selectedScheme) || 0)
           + '';
-    },
+    }
 
     _computeShowTabs(schemes) {
       return schemes.length > 1 ? '' : 'hidden';
-    },
-  });
+    }
+  }
+
+  customElements.define(GrDownloadCommands.is, GrDownloadCommands);
 })();
diff --git a/polygerrit-ui/app/elements/shared/gr-download-commands/gr-download-commands_test.html b/polygerrit-ui/app/elements/shared/gr-download-commands/gr-download-commands_test.html
index 85a5b1f..7d4b341 100644
--- a/polygerrit-ui/app/elements/shared/gr-download-commands/gr-download-commands_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-download-commands/gr-download-commands_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-download-commands</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>
diff --git a/polygerrit-ui/app/elements/shared/gr-dropdown-list/gr-dropdown-list.js b/polygerrit-ui/app/elements/shared/gr-dropdown-list/gr-dropdown-list.js
index 1f0d01c..2197733 100644
--- a/polygerrit-ui/app/elements/shared/gr-dropdown-list/gr-dropdown-list.js
+++ b/polygerrit-ui/app/elements/shared/gr-dropdown-list/gr-dropdown-list.js
@@ -45,9 +45,10 @@
    */
   Defs.item;
 
-  Polymer({
-    is: 'gr-dropdown-list',
-
+  class GrDropdownList extends Polymer.GestureEventListeners(
+      Polymer.LegacyElementMixin(
+          Polymer.Element)) {
+    static get is() { return 'gr-dropdown-list'; }
     /**
      * Fired when the selected value changes
      *
@@ -56,24 +57,28 @@
      * @property {string|number} value
      */
 
-    properties: {
-      initialCount: Number,
-      /** @type {!Array<!Defs.item>} */
-      items: Object,
-      text: String,
-      disabled: {
-        type: Boolean,
-        value: false,
-      },
-      value: {
-        type: String,
-        notify: true,
-      },
-    },
+    static get properties() {
+      return {
+        initialCount: Number,
+        /** @type {!Array<!Defs.item>} */
+        items: Object,
+        text: String,
+        disabled: {
+          type: Boolean,
+          value: false,
+        },
+        value: {
+          type: String,
+          notify: true,
+        },
+      };
+    }
 
-    observers: [
-      '_handleValueChange(value, items)',
-    ],
+    static get observers() {
+      return [
+        '_handleValueChange(value, items)',
+      ];
+    }
 
     /**
      * Handle a click on the iron-dropdown element.
@@ -85,7 +90,7 @@
       this.async(() => {
         this.$.dropdown.close();
       }, 1);
-    },
+    }
 
     /**
      * Handle a click on the button to open the dropdown.
@@ -93,18 +98,18 @@
      */
     _showDropdownTapHandler(e) {
       this._open();
-    },
+    }
 
     /**
      * Open the dropdown.
      */
     _open() {
       this.$.dropdown.open();
-    },
+    }
 
     _computeMobileText(item) {
       return item.mobileText ? item.mobileText : item.text;
-    },
+    }
 
     _handleValueChange(value, items) {
       // Polymer 2: check for undefined
@@ -123,6 +128,8 @@
         detail: {value},
         bubbles: false,
       }));
-    },
-  });
+    }
+  }
+
+  customElements.define(GrDropdownList.is, GrDropdownList);
 })();
diff --git a/polygerrit-ui/app/elements/shared/gr-dropdown-list/gr-dropdown-list_test.html b/polygerrit-ui/app/elements/shared/gr-dropdown-list/gr-dropdown-list_test.html
index b5b1408..8d098df 100644
--- a/polygerrit-ui/app/elements/shared/gr-dropdown-list/gr-dropdown-list_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-dropdown-list/gr-dropdown-list_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-dropdown-list</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>
diff --git a/polygerrit-ui/app/elements/shared/gr-dropdown/gr-dropdown.js b/polygerrit-ui/app/elements/shared/gr-dropdown/gr-dropdown.js
index 7273268..9725f0c 100644
--- a/polygerrit-ui/app/elements/shared/gr-dropdown/gr-dropdown.js
+++ b/polygerrit-ui/app/elements/shared/gr-dropdown/gr-dropdown.js
@@ -20,9 +20,17 @@
   const REL_NOOPENER = 'noopener';
   const REL_EXTERNAL = 'external';
 
-  Polymer({
-    is: 'gr-dropdown',
-
+  /**
+    * @appliesMixin Gerrit.BaseUrlMixin
+    * @appliesMixin Gerrit.KeyboardShortcutMixin
+    */
+  class GrDropdown extends Polymer.mixinBehaviors( [
+    Gerrit.BaseUrlBehavior,
+    Gerrit.KeyboardShortcutBehavior,
+  ], Polymer.GestureEventListeners(
+      Polymer.LegacyElementMixin(
+          Polymer.Element))) {
+    static get is() { return 'gr-dropdown'; }
     /**
      * Fired when a non-link dropdown item with the given ID is tapped.
      *
@@ -35,60 +43,59 @@
      * @event tap-item
      */
 
-    properties: {
-      items: {
-        type: Array,
-        observer: '_resetCursorStops',
-      },
-      downArrow: Boolean,
-      topContent: Object,
-      horizontalAlign: {
-        type: String,
-        value: 'left',
-      },
+    static get properties() {
+      return {
+        items: {
+          type: Array,
+          observer: '_resetCursorStops',
+        },
+        downArrow: Boolean,
+        topContent: Object,
+        horizontalAlign: {
+          type: String,
+          value: 'left',
+        },
 
-      /**
+        /**
        * Style the dropdown trigger as a link (rather than a button).
        */
-      link: {
-        type: Boolean,
-        value: false,
-      },
+        link: {
+          type: Boolean,
+          value: false,
+        },
 
-      verticalOffset: {
-        type: Number,
-        value: 40,
-      },
+        verticalOffset: {
+          type: Number,
+          value: 40,
+        },
 
-      /**
+        /**
        * List the IDs of dropdown buttons to be disabled. (Note this only
        * diisables bittons and not link entries.)
        */
-      disabledIds: {
-        type: Array,
-        value() { return []; },
-      },
+        disabledIds: {
+          type: Array,
+          value() { return []; },
+        },
 
-      /**
+        /**
        * The elements of the list.
        */
-      _listElements: {
-        type: Array,
-        value() { return []; },
-      },
-    },
+        _listElements: {
+          type: Array,
+          value() { return []; },
+        },
+      };
+    }
 
-    behaviors: [
-      Gerrit.BaseUrlBehavior,
-      Gerrit.KeyboardShortcutBehavior,
-    ],
-
-    keyBindings: {
-      'down': '_handleDown',
-      'enter space': '_handleEnter',
-      'tab': '_handleTab',
-      'up': '_handleUp',
-    },
+    get keyBindings() {
+      return {
+        'down': '_handleDown',
+        'enter space': '_handleEnter',
+        'tab': '_handleTab',
+        'up': '_handleUp',
+      };
+    }
 
     /**
      * Handle the up key.
@@ -102,7 +109,7 @@
       } else {
         this._open();
       }
-    },
+    }
 
     /**
      * Handle the down key.
@@ -116,7 +123,7 @@
       } else {
         this._open();
       }
-    },
+    }
 
     /**
      * Handle the tab key.
@@ -128,7 +135,7 @@
         e.preventDefault();
         e.stopPropagation();
       }
-    },
+    }
 
     /**
      * Handle the enter key.
@@ -146,7 +153,7 @@
       } else {
         this._open();
       }
-    },
+    }
 
     /**
      * Handle a click on the iron-dropdown element.
@@ -154,7 +161,7 @@
      */
     _handleDropdownTap(e) {
       this._close();
-    },
+    }
 
     /**
      * Hanlde a click on the button to open the dropdown.
@@ -168,7 +175,7 @@
       } else {
         this._open();
       }
-    },
+    }
 
     /**
      * Open the dropdown and initialize the cursor.
@@ -178,7 +185,7 @@
       this._resetCursorStops();
       this.$.cursor.setCursorAtIndex(0);
       this.$.cursor.target.focus();
-    },
+    }
 
     _close() {
       // async is needed so that that the click event is fired before the
@@ -186,7 +193,7 @@
       this.async(() => {
         this.$.dropdown.close();
       }, 1);
-    },
+    }
 
     /**
      * Get the class for a top-content item based on the given boolean.
@@ -195,7 +202,7 @@
      */
     _getClassIfBold(bold) {
       return bold ? 'bold-text' : '';
-    },
+    }
 
     /**
      * Build a URL for the given host and path. The base URL will be only added,
@@ -208,7 +215,7 @@
       const base = path.startsWith(this.getBaseUrl()) ?
         '' : this.getBaseUrl();
       return '//' + host + base + path;
-    },
+    }
 
     /**
      * Build a scheme-relative URL for the current host. Will include the base
@@ -220,7 +227,7 @@
     _computeRelativeURL(path) {
       const host = window.location.host;
       return this._computeURLHelper(host, path);
-    },
+    }
 
     /**
      * Compute the URL for a link object.
@@ -235,7 +242,7 @@
         return link.url;
       }
       return this._computeRelativeURL(link.url);
-    },
+    }
 
     /**
      * Compute the value for the rel attribute of an anchor for the given link
@@ -249,7 +256,7 @@
       if (link.target) { return REL_NOOPENER; }
       if (link.external) { return REL_EXTERNAL; }
       return null;
-    },
+    }
 
     /**
      * Handle a click on an item of the dropdown.
@@ -264,7 +271,7 @@
         }
         this.dispatchEvent(new CustomEvent('tap-item-' + id));
       }
-    },
+    }
 
     /**
      * If a dropdown item is shown as a button, get the class for the button.
@@ -275,7 +282,7 @@
      */
     _computeDisabledClass(id, disabledIdsRecord) {
       return disabledIdsRecord.base.includes(id) ? 'disabled' : '';
-    },
+    }
 
     /**
      * Recompute the stops for the dropdown item cursor.
@@ -286,14 +293,16 @@
         this._listElements = Array.from(
             Polymer.dom(this.root).querySelectorAll('li'));
       }
-    },
+    }
 
     _computeHasTooltip(tooltip) {
       return !!tooltip;
-    },
+    }
 
     _computeIsDownload(link) {
       return !!link.download;
-    },
-  });
+    }
+  }
+
+  customElements.define(GrDropdown.is, GrDropdown);
 })();
diff --git a/polygerrit-ui/app/elements/shared/gr-dropdown/gr-dropdown_test.html b/polygerrit-ui/app/elements/shared/gr-dropdown/gr-dropdown_test.html
index 295f746..4b5ccb4 100644
--- a/polygerrit-ui/app/elements/shared/gr-dropdown/gr-dropdown_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-dropdown/gr-dropdown_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-dropdown</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>
diff --git a/polygerrit-ui/app/elements/shared/gr-editable-content/gr-editable-content.js b/polygerrit-ui/app/elements/shared/gr-editable-content/gr-editable-content.js
index 75c0201..49b1025 100644
--- a/polygerrit-ui/app/elements/shared/gr-editable-content/gr-editable-content.js
+++ b/polygerrit-ui/app/elements/shared/gr-editable-content/gr-editable-content.js
@@ -20,9 +20,15 @@
   const RESTORED_MESSAGE = 'Content restored from a previous edit.';
   const STORAGE_DEBOUNCE_INTERVAL_MS = 400;
 
-  Polymer({
-    is: 'gr-editable-content',
-
+  /**
+    * @appliesMixin Gerrit.FireMixin
+    */
+  class GrEditableContent extends Polymer.mixinBehaviors( [
+    Gerrit.FireBehavior,
+  ], Polymer.GestureEventListeners(
+      Polymer.LegacyElementMixin(
+          Polymer.Element))) {
+    static get is() { return 'gr-editable-content'; }
     /**
      * Fired when the save button is pressed.
      *
@@ -41,42 +47,40 @@
      * @event show-alert
      */
 
-    properties: {
-      content: {
-        notify: true,
-        type: String,
-      },
-      disabled: {
-        reflectToAttribute: true,
-        type: Boolean,
-        value: false,
-      },
-      editing: {
-        observer: '_editingChanged',
-        type: Boolean,
-        value: false,
-      },
-      removeZeroWidthSpace: Boolean,
-      // If no storage key is provided, content is not stored.
-      storageKey: String,
-      _saveDisabled: {
-        computed: '_computeSaveDisabled(disabled, content, _newContent)',
-        type: Boolean,
-        value: true,
-      },
-      _newContent: {
-        type: String,
-        observer: '_newContentChanged',
-      },
-    },
-
-    behaviors: [
-      Gerrit.FireBehavior,
-    ],
+    static get properties() {
+      return {
+        content: {
+          notify: true,
+          type: String,
+        },
+        disabled: {
+          reflectToAttribute: true,
+          type: Boolean,
+          value: false,
+        },
+        editing: {
+          observer: '_editingChanged',
+          type: Boolean,
+          value: false,
+        },
+        removeZeroWidthSpace: Boolean,
+        // If no storage key is provided, content is not stored.
+        storageKey: String,
+        _saveDisabled: {
+          computed: '_computeSaveDisabled(disabled, content, _newContent)',
+          type: Boolean,
+          value: true,
+        },
+        _newContent: {
+          type: String,
+          observer: '_newContentChanged',
+        },
+      };
+    }
 
     focusTextarea() {
       this.$$('iron-autogrow-textarea').textarea.focus();
-    },
+    }
 
     _newContentChanged(newContent, oldContent) {
       if (!this.storageKey) { return; }
@@ -88,7 +92,7 @@
           this.$.storage.eraseEditableContentItem(this.storageKey);
         }
       }, STORAGE_DEBOUNCE_INTERVAL_MS);
-    },
+    }
 
     _editingChanged(editing) {
       if (!editing) { return; }
@@ -114,7 +118,7 @@
       this._newContent = this.removeZeroWidthSpace ?
         content.replace(/^R=\u200B/gm, 'R=') :
         content;
-    },
+    }
 
     _computeSaveDisabled(disabled, content, newContent) {
       // Polymer 2: check for undefined
@@ -127,17 +131,19 @@
       }
 
       return disabled || (content === newContent);
-    },
+    }
 
     _handleSave(e) {
       e.preventDefault();
       this.fire('editable-content-save', {content: this._newContent});
-    },
+    }
 
     _handleCancel(e) {
       e.preventDefault();
       this.editing = false;
       this.fire('editable-content-cancel');
-    },
-  });
+    }
+  }
+
+  customElements.define(GrEditableContent.is, GrEditableContent);
 })();
diff --git a/polygerrit-ui/app/elements/shared/gr-editable-content/gr-editable-content_test.html b/polygerrit-ui/app/elements/shared/gr-editable-content/gr-editable-content_test.html
index 24db69f..69c9a00 100644
--- a/polygerrit-ui/app/elements/shared/gr-editable-content/gr-editable-content_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-editable-content/gr-editable-content_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-editable-content</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>
diff --git a/polygerrit-ui/app/elements/shared/gr-editable-label/gr-editable-label.js b/polygerrit-ui/app/elements/shared/gr-editable-label/gr-editable-label.js
index 4485551..b989401 100644
--- a/polygerrit-ui/app/elements/shared/gr-editable-label/gr-editable-label.js
+++ b/polygerrit-ui/app/elements/shared/gr-editable-label/gr-editable-label.js
@@ -20,76 +20,84 @@
   const AWAIT_MAX_ITERS = 10;
   const AWAIT_STEP = 5;
 
-  Polymer({
-    is: 'gr-editable-label',
-
+  /**
+    * @appliesMixin Gerrit.FireMixin
+    * @appliesMixin Gerrit.KeyboardShortcutMixin
+    */
+  class GrEditableLabel extends Polymer.mixinBehaviors( [
+    Gerrit.FireBehavior,
+    Gerrit.KeyboardShortcutBehavior,
+  ], Polymer.GestureEventListeners(
+      Polymer.LegacyElementMixin(
+          Polymer.Element))) {
+    static get is() { return 'gr-editable-label'; }
     /**
      * Fired when the value is changed.
      *
      * @event changed
      */
 
-    properties: {
-      labelText: String,
-      editing: {
-        type: Boolean,
-        value: false,
-      },
-      value: {
-        type: String,
-        notify: true,
-        value: '',
-        observer: '_updateTitle',
-      },
-      placeholder: {
-        type: String,
-        value: '',
-      },
-      readOnly: {
-        type: Boolean,
-        value: false,
-      },
-      uppercase: {
-        type: Boolean,
-        reflectToAttribute: true,
-        value: false,
-      },
-      maxLength: Number,
-      _inputText: String,
-      // This is used to push the iron-input element up on the page, so
-      // the input is placed in approximately the same position as the
-      // trigger.
-      _verticalOffset: {
-        type: Number,
-        readOnly: true,
-        value: -30,
-      },
-    },
+    static get properties() {
+      return {
+        labelText: String,
+        editing: {
+          type: Boolean,
+          value: false,
+        },
+        value: {
+          type: String,
+          notify: true,
+          value: '',
+          observer: '_updateTitle',
+        },
+        placeholder: {
+          type: String,
+          value: '',
+        },
+        readOnly: {
+          type: Boolean,
+          value: false,
+        },
+        uppercase: {
+          type: Boolean,
+          reflectToAttribute: true,
+          value: false,
+        },
+        maxLength: Number,
+        _inputText: String,
+        // This is used to push the iron-input element up on the page, so
+        // the input is placed in approximately the same position as the
+        // trigger.
+        _verticalOffset: {
+          type: Number,
+          readOnly: true,
+          value: -30,
+        },
+      };
+    }
 
-    behaviors: [
-      Gerrit.FireBehavior,
-      Gerrit.KeyboardShortcutBehavior,
-    ],
+    ready() {
+      super.ready();
+      this._ensureAttribute('tabindex', '0');
+    }
 
-    keyBindings: {
-      enter: '_handleEnter',
-      esc: '_handleEsc',
-    },
-
-    hostAttributes: {
-      tabindex: '0',
-    },
+    get keyBindings() {
+      return {
+        enter: '_handleEnter',
+        esc: '_handleEsc',
+      };
+    }
 
     _usePlaceholder(value, placeholder) {
       return (!value || !value.length) && placeholder;
-    },
+    }
 
     _computeLabel(value, placeholder) {
       if (this._usePlaceholder(value, placeholder)) {
         return placeholder;
       }
       return value;
-    },
+    }
 
     _showDropdown() {
       if (this.readOnly || this.editing) { return; }
@@ -98,13 +106,13 @@
         if (!this.$.input.value) { return; }
         this._nativeInput.setSelectionRange(0, this.$.input.value.length);
       });
-    },
+    }
 
     open() {
       return this._open().then(() => {
         this._nativeInput.focus();
       });
-    },
+    }
 
     _open(...args) {
       this.$.dropdown.open();
@@ -115,7 +123,7 @@
         Polymer.IronOverlayBehaviorImpl.open.apply(this.$.dropdown, args);
         this._awaitOpen(resolve);
       });
-    },
+    }
 
     /**
      * NOTE: (wyatta) Slightly hacky way to listen to the overlay actually
@@ -133,11 +141,11 @@
         }, AWAIT_STEP);
       };
       step.call(this);
-    },
+    }
 
     _id() {
       return this.getAttribute('id') || 'global';
-    },
+    }
 
     _save() {
       if (!this.editing) { return; }
@@ -145,20 +153,20 @@
       this.value = this._inputText;
       this.editing = false;
       this.fire('changed', this.value);
-    },
+    }
 
     _cancel() {
       if (!this.editing) { return; }
       this.$.dropdown.close();
       this.editing = false;
       this._inputText = this.value;
-    },
+    }
 
     get _nativeInput() {
       // In Polymer 2, the namespace of nativeInput
       // changed from input to nativeInput
       return this.$.input.$.nativeInput || this.$.input.$.input;
-    },
+    }
 
     _handleEnter(e) {
       e = this.getKeyboardEvent(e);
@@ -167,7 +175,7 @@
         e.preventDefault();
         this._save();
       }
-    },
+    }
 
     _handleEsc(e) {
       e = this.getKeyboardEvent(e);
@@ -176,7 +184,7 @@
         e.preventDefault();
         this._cancel();
       }
-    },
+    }
 
     _computeLabelClass(readOnly, value, placeholder) {
       const classes = [];
@@ -185,10 +193,12 @@
         classes.push('placeholder');
       }
       return classes.join(' ');
-    },
+    }
 
     _updateTitle(value) {
       this.setAttribute('title', this._computeLabel(value, this.placeholder));
-    },
-  });
+    }
+  }
+
+  customElements.define(GrEditableLabel.is, GrEditableLabel);
 })();
diff --git a/polygerrit-ui/app/elements/shared/gr-editable-label/gr-editable-label_test.html b/polygerrit-ui/app/elements/shared/gr-editable-label/gr-editable-label_test.html
index 416a499..1690c3a 100644
--- a/polygerrit-ui/app/elements/shared/gr-editable-label/gr-editable-label_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-editable-label/gr-editable-label_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-editable-label</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>
diff --git a/polygerrit-ui/app/elements/shared/gr-fixed-panel/gr-fixed-panel.js b/polygerrit-ui/app/elements/shared/gr-fixed-panel/gr-fixed-panel.js
index 2c32709..af533bc 100644
--- a/polygerrit-ui/app/elements/shared/gr-fixed-panel/gr-fixed-panel.js
+++ b/polygerrit-ui/app/elements/shared/gr-fixed-panel/gr-fixed-panel.js
@@ -17,47 +17,52 @@
 (function() {
   'use strict';
 
-  Polymer({
-    is: 'gr-fixed-panel',
+  class GrFixedPanel extends Polymer.GestureEventListeners(
+      Polymer.LegacyElementMixin(
+          Polymer.Element)) {
+    static get is() { return 'gr-fixed-panel'; }
 
-    properties: {
-      floatingDisabled: Boolean,
-      readyForMeasure: {
-        type: Boolean,
-        observer: '_readyForMeasureObserver',
-      },
-      keepOnScroll: {
-        type: Boolean,
-        value: false,
-      },
-      _isMeasured: {
-        type: Boolean,
-        value: false,
-      },
+    static get properties() {
+      return {
+        floatingDisabled: Boolean,
+        readyForMeasure: {
+          type: Boolean,
+          observer: '_readyForMeasureObserver',
+        },
+        keepOnScroll: {
+          type: Boolean,
+          value: false,
+        },
+        _isMeasured: {
+          type: Boolean,
+          value: false,
+        },
 
-      /**
+        /**
        * Initial offset from the top of the document, in pixels.
        */
-      _topInitial: Number,
+        _topInitial: Number,
 
-      /**
+        /**
        * Current offset from the top of the window, in pixels.
        */
-      _topLast: Number,
+        _topLast: Number,
 
-      _headerHeight: Number,
-      _headerFloating: {
-        type: Boolean,
-        value: false,
-      },
-      _observer: {
-        type: Object,
-        value: null,
-      },
-      _webComponentsReady: Boolean,
-    },
+        _headerHeight: Number,
+        _headerFloating: {
+          type: Boolean,
+          value: false,
+        },
+        _observer: {
+          type: Object,
+          value: null,
+        },
+        _webComponentsReady: Boolean,
+      };
+    }
 
     attached() {
+      super.attached();
       if (this.floatingDisabled) {
         return;
       }
@@ -69,21 +74,22 @@
       this.listen(window, 'scroll', '_updateOnScroll');
       this._observer = new MutationObserver(this.update.bind(this));
       this._observer.observe(this.$.header, {childList: true, subtree: true});
-    },
+    }
 
     detached() {
+      super.detached();
       this.unlisten(window, 'scroll', '_updateOnScroll');
       this.unlisten(window, 'resize', 'update');
       if (this._observer) {
         this._observer.disconnect();
       }
-    },
+    }
 
     _readyForMeasureObserver(readyForMeasure) {
       if (readyForMeasure) {
         this.update();
       }
-    },
+    }
 
     _computeHeaderClass(headerFloating, topLast) {
       const fixedAtTop = this.keepOnScroll && topLast === 0;
@@ -91,7 +97,7 @@
         headerFloating ? 'floating' : '',
         fixedAtTop ? 'fixedAtTop' : '',
       ].join(' ');
-    },
+    }
 
     unfloat() {
       if (this.floatingDisabled) {
@@ -100,19 +106,19 @@
       this.$.header.style.top = '';
       this._headerFloating = false;
       this.updateStyles({'--header-height': ''});
-    },
+    }
 
     update() {
       this.debounce('update', () => {
         this._updateDebounced();
       }, 100);
-    },
+    }
 
     _updateOnScroll() {
       this.debounce('update', () => {
         this._updateDebounced();
       });
-    },
+    }
 
     _updateDebounced() {
       if (this.floatingDisabled) {
@@ -121,11 +127,11 @@
       this._isMeasured = false;
       this._maybeFloatHeader();
       this._reposition();
-    },
+    }
 
     _getElementTop() {
       return this.getBoundingClientRect().top;
-    },
+    }
 
     _reposition() {
       if (!this._headerFloating) {
@@ -155,7 +161,7 @@
         }
         this._topLast = newTop;
       }
-    },
+    }
 
     _measure() {
       if (this._isMeasured) {
@@ -171,12 +177,12 @@
       this._topInitial =
         this.getBoundingClientRect().top + document.body.scrollTop;
       this._isMeasured = true;
-    },
+    }
 
     _isFloatingNeeded() {
       return this.keepOnScroll ||
         document.body.scrollWidth > document.body.clientWidth;
-    },
+    }
 
     _maybeFloatHeader() {
       if (!this._isFloatingNeeded()) {
@@ -186,11 +192,13 @@
       if (this._isMeasured) {
         this._floatHeader();
       }
-    },
+    }
 
     _floatHeader() {
       this.updateStyles({'--header-height': this._headerHeight + 'px'});
       this._headerFloating = true;
-    },
-  });
+    }
+  }
+
+  customElements.define(GrFixedPanel.is, GrFixedPanel);
 })();
diff --git a/polygerrit-ui/app/elements/shared/gr-fixed-panel/gr-fixed-panel_test.html b/polygerrit-ui/app/elements/shared/gr-fixed-panel/gr-fixed-panel_test.html
index 75e9901..419b3e7 100644
--- a/polygerrit-ui/app/elements/shared/gr-fixed-panel/gr-fixed-panel_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-fixed-panel/gr-fixed-panel_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-fixed-panel</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>
diff --git a/polygerrit-ui/app/elements/shared/gr-formatted-text/gr-formatted-text.js b/polygerrit-ui/app/elements/shared/gr-formatted-text/gr-formatted-text.js
index 4e68d42..7730a75 100644
--- a/polygerrit-ui/app/elements/shared/gr-formatted-text/gr-formatted-text.js
+++ b/polygerrit-ui/app/elements/shared/gr-formatted-text/gr-formatted-text.js
@@ -20,30 +20,37 @@
   // eslint-disable-next-line no-unused-vars
   const QUOTE_MARKER_PATTERN = /\n\s?>\s/g;
 
-  Polymer({
-    is: 'gr-formatted-text',
+  class GrFormattedText extends Polymer.GestureEventListeners(
+      Polymer.LegacyElementMixin(
+          Polymer.Element)) {
+    static get is() { return 'gr-formatted-text'; }
 
-    properties: {
-      content: {
-        type: String,
-        observer: '_contentChanged',
-      },
-      config: Object,
-      noTrailingMargin: {
-        type: Boolean,
-        value: false,
-      },
-    },
+    static get properties() {
+      return {
+        content: {
+          type: String,
+          observer: '_contentChanged',
+        },
+        config: Object,
+        noTrailingMargin: {
+          type: Boolean,
+          value: false,
+        },
+      };
+    }
 
-    observers: [
-      '_contentOrConfigChanged(content, config)',
-    ],
+    static get observers() {
+      return [
+        '_contentOrConfigChanged(content, config)',
+      ];
+    }
 
     ready() {
+      super.ready();
       if (this.noTrailingMargin) {
         this.classList.add('noTrailingMargin');
       }
-    },
+    }
 
     /**
      * Get the plain text as it appears in the generated DOM.
@@ -56,7 +63,7 @@
      */
     getTextContent() {
       return this._blocksToText(this._computeBlocks(this.content));
-    },
+    }
 
     _contentChanged(content) {
       // In the case where the config may not be set (perhaps due to the
@@ -64,7 +71,7 @@
       // prevent waiting on the config to display the text.
       if (this.config) { return; }
       this._contentOrConfigChanged(content);
-    },
+    }
 
     /**
      * Given a source string, update the DOM inside #container.
@@ -81,7 +88,7 @@
       for (const node of this._computeNodes(this._computeBlocks(content))) {
         container.appendChild(node);
       }
-    },
+    }
 
     /**
      * Given a source string, parse into an array of block objects. Each block
@@ -127,7 +134,7 @@
         }
       }
       return result;
-    },
+    }
 
     /**
      * Take a block of comment text that contains a list and potentially
@@ -201,7 +208,7 @@
       if (block !== null) {
         out.push(block);
       }
-    },
+    }
 
     _makeQuote(p) {
       const quotedLines = p
@@ -212,21 +219,21 @@
         type: 'quote',
         blocks: this._computeBlocks(quotedLines),
       };
-    },
+    }
 
     _isQuote(p) {
       return p.startsWith('> ') || p.startsWith(' > ');
-    },
+    }
 
     _isPreFormat(p) {
       return p.includes('\n ') || p.includes('\n\t') ||
           p.startsWith(' ') || p.startsWith('\t');
-    },
+    }
 
     _isList(p) {
       return p.includes('\n- ') || p.includes('\n* ') ||
           p.startsWith('- ') || p.startsWith('* ');
-    },
+    }
 
     /**
      * @param {string} content
@@ -241,7 +248,7 @@
         text.classList.add('pre');
       }
       return text;
-    },
+    }
 
     /**
      * Map an array of block objects to an array of DOM nodes.
@@ -278,7 +285,7 @@
           return ul;
         }
       });
-    },
+    }
 
     _blocksToText(blocks) {
       return blocks.map(block => {
@@ -292,6 +299,8 @@
           return block.items.join('\n');
         }
       }).join('\n\n');
-    },
-  });
+    }
+  }
+
+  customElements.define(GrFormattedText.is, GrFormattedText);
 })();
diff --git a/polygerrit-ui/app/elements/shared/gr-formatted-text/gr-formatted-text_test.html b/polygerrit-ui/app/elements/shared/gr-formatted-text/gr-formatted-text_test.html
index 801190a..92fbc74 100644
--- a/polygerrit-ui/app/elements/shared/gr-formatted-text/gr-formatted-text_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-formatted-text/gr-formatted-text_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-editable-label</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>
diff --git a/polygerrit-ui/app/elements/shared/gr-hovercard/gr-hovercard.js b/polygerrit-ui/app/elements/shared/gr-hovercard/gr-hovercard.js
index 3a43191..5e3cfb3 100644
--- a/polygerrit-ui/app/elements/shared/gr-hovercard/gr-hovercard.js
+++ b/polygerrit-ui/app/elements/shared/gr-hovercard/gr-hovercard.js
@@ -25,82 +25,90 @@
    */
   const DIAGONAL_OVERFLOW = 15;
 
-  Polymer({
-    is: 'gr-hovercard',
+  class GrHovercard extends Polymer.GestureEventListeners(
+      Polymer.LegacyElementMixin(
+          Polymer.Element)) {
+    static get is() { return 'gr-hovercard'; }
 
-    properties: {
+    static get properties() {
+      return {
       /**
        * @type {?}
        */
-      _target: Object,
+        _target: Object,
 
-      /**
+        /**
        * Determines whether or not the hovercard is visible.
        *
        * @type {boolean}
        */
-      _isShowing: {
-        type: Boolean,
-        value: false,
-      },
-      /**
+        _isShowing: {
+          type: Boolean,
+          value: false,
+        },
+        /**
        * The `id` of the element that the hovercard is anchored to.
        *
        * @type {string}
        */
-      for: {
-        type: String,
-        observer: '_forChanged',
-      },
+        for: {
+          type: String,
+          observer: '_forChanged',
+        },
 
-      /**
+        /**
        * The spacing between the top of the hovercard and the element it is
        * anchored to.
        *
        * @type {number}
        */
-      offset: {
-        type: Number,
-        value: 14,
-      },
+        offset: {
+          type: Number,
+          value: 14,
+        },
 
-      /**
+        /**
        * Positions the hovercard to the top, right, bottom, left, bottom-left,
        * bottom-right, top-left, or top-right of its content.
        *
        * @type {string}
        */
-      position: {
-        type: String,
-        value: 'bottom',
-      },
+        position: {
+          type: String,
+          value: 'bottom',
+        },
 
-      container: Object,
-      /**
+        container: Object,
+        /**
        * ID for the container element.
        *
        * @type {string}
        */
-      containerId: {
-        type: String,
-        value: 'gr-hovercard-container',
-      },
-    },
-
-    listeners: {
-      mouseleave: 'hide',
-    },
+        containerId: {
+          type: String,
+          value: 'gr-hovercard-container',
+        },
+      };
+    }
 
     attached() {
+      super.attached();
       if (!this._target) { this._target = this.target; }
       this.listen(this._target, 'mouseenter', 'show');
       this.listen(this._target, 'focus', 'show');
       this.listen(this._target, 'mouseleave', 'hide');
       this.listen(this._target, 'blur', 'hide');
       this.listen(this._target, 'click', 'hide');
-    },
+    }
+
+    created() {
+      super.created();
+      this.addEventListener('mouseleave',
+          e => this.hide(e));
+    }
 
     ready() {
+      super.ready();
       // First, check to see if the container has already been created.
       this.container = Gerrit.getRootElement()
           .querySelector('#' + this.containerId);
@@ -111,7 +119,7 @@
       this.container = document.createElement('div');
       this.container.setAttribute('id', this.containerId);
       Gerrit.getRootElement().appendChild(this.container);
-    },
+    }
 
     removeListeners() {
       this.unlisten(this._target, 'mouseenter', 'show');
@@ -119,7 +127,7 @@
       this.unlisten(this._target, 'mouseleave', 'hide');
       this.unlisten(this._target, 'blur', 'hide');
       this.unlisten(this._target, 'click', 'hide');
-    },
+    }
 
     /**
      * Returns the target element that the hovercard is anchored to (the `id` of
@@ -140,7 +148,7 @@
           parentNode;
       }
       return target;
-    },
+    }
 
     /**
      * Hides/closes the hovercard. This occurs when the user triggers the
@@ -184,7 +192,7 @@
       if (this.container.contains(this)) {
         this.container.removeChild(this);
       }
-    },
+    }
 
     /**
      * Shows/opens the hovercard. This occurs when the user triggers the
@@ -207,7 +215,7 @@
 
       // Trigger the transition
       this.classList.add(HOVER_CLASS);
-    },
+    }
 
     /**
      * Updates the hovercard's position based on the `position` attribute
@@ -306,7 +314,7 @@
       // Set the hovercard's position
       cssText += `left:${hovercardLeft}px; top:${hovercardTop}px;`;
       this.style.cssText = cssText;
-    },
+    }
 
     /**
      * Responds to a change in the `for` value and gets the updated `target`
@@ -316,6 +324,8 @@
      */
     _forChanged() {
       this._target = this.target;
-    },
-  });
+    }
+  }
+
+  customElements.define(GrHovercard.is, GrHovercard);
 })();
diff --git a/polygerrit-ui/app/elements/shared/gr-hovercard/gr-hovercard_test.html b/polygerrit-ui/app/elements/shared/gr-hovercard/gr-hovercard_test.html
index 8e79f65..35d736c 100644
--- a/polygerrit-ui/app/elements/shared/gr-hovercard/gr-hovercard_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-hovercard/gr-hovercard_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-hovercard</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>
diff --git a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-annotation-actions-context_test.html b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-annotation-actions-context_test.html
index 3223636..66e7a74 100644
--- a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-annotation-actions-context_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-annotation-actions-context_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-annotation-actions-context</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>
diff --git a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-annotation-actions-js-api_test.html b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-annotation-actions-js-api_test.html
index 987b551..98ff954 100644
--- a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-annotation-actions-js-api_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-annotation-actions-js-api_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-annotation-actions-js-api-js-api</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>
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
index 89dfbd7..128738d 100644
--- 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
@@ -18,7 +18,7 @@
 
 <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>
diff --git a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-change-actions-js-api_test.html b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-change-actions-js-api_test.html
index 7332877..cbe2d37 100644
--- a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-change-actions-js-api_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-change-actions-js-api_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-change-actions-js-api</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>
diff --git a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-change-reply-js-api_test.html b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-change-reply-js-api_test.html
index 842a2fe..7955aa4 100644
--- a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-change-reply-js-api_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-change-reply-js-api_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-change-reply-js-api</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>
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
index e81b8aa..7b03308 100644
--- 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
@@ -18,7 +18,7 @@
 
 <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>
diff --git a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-js-api-interface.js b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-js-api-interface.js
index 3206b21..1802a4a 100644
--- a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-js-api-interface.js
+++ b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-js-api-interface.js
@@ -37,24 +37,34 @@
     REPLY_DIALOG: 'replydialog',
   };
 
-  Polymer({
-    is: 'gr-js-api-interface',
+  /**
+    * @appliesMixin Gerrit.PatchSetMixin
+    */
+  class GrJsApiInterface extends Polymer.mixinBehaviors( [
+    Gerrit.PatchSetBehavior,
+  ], Polymer.GestureEventListeners(
+      Polymer.LegacyElementMixin(
+          Polymer.Element))) {
+    static get is() { return 'gr-js-api-interface'; }
 
-    properties: {
-      _elements: {
-        type: Object,
-        value: {}, // Shared across all instances.
-      },
-      _eventCallbacks: {
-        type: Object,
-        value: {}, // Shared across all instances.
-      },
-    },
+    constructor() {
+      super();
+      this.Element = Element;
+      this.EventType = EventType;
+    }
 
-    behaviors: [Gerrit.PatchSetBehavior],
-
-    Element,
-    EventType,
+    static get properties() {
+      return {
+        _elements: {
+          type: Object,
+          value: {}, // Shared across all instances.
+        },
+        _eventCallbacks: {
+          type: Object,
+          value: {}, // Shared across all instances.
+        },
+      };
+    }
 
     handleEvent(type, detail) {
       Gerrit.awaitPluginsLoaded().then(() => {
@@ -80,22 +90,22 @@
             break;
         }
       });
-    },
+    }
 
     addElement(key, el) {
       this._elements[key] = el;
-    },
+    }
 
     getElement(key) {
       return this._elements[key];
-    },
+    }
 
     addEventCallback(eventName, callback) {
       if (!this._eventCallbacks[eventName]) {
         this._eventCallbacks[eventName] = [];
       }
       this._eventCallbacks[eventName].push(callback);
-    },
+    }
 
     canSubmitChange(change, revision) {
       const submitCallbacks = this._getEventCallbacks(EventType.SUBMIT_CHANGE);
@@ -109,14 +119,14 @@
       });
 
       return !cancelSubmit;
-    },
+    }
 
     _removeEventCallbacks() {
       for (const k in EventType) {
         if (!EventType.hasOwnProperty(k)) { continue; }
         this._eventCallbacks[EventType[k]] = [];
       }
-    },
+    }
 
     _handleHistory(detail) {
       for (const cb of this._getEventCallbacks(EventType.HISTORY)) {
@@ -126,7 +136,7 @@
           console.error(err);
         }
       }
-    },
+    }
 
     _handleShowChange(detail) {
       // Note (issue 8221) Shallow clone the change object and add a mergeable
@@ -161,7 +171,7 @@
           console.error(err);
         }
       }
-    },
+    }
 
     handleCommitMessage(change, msg) {
       for (const cb of this._getEventCallbacks(EventType.COMMIT_MSG_EDIT)) {
@@ -171,7 +181,7 @@
           console.error(err);
         }
       }
-    },
+    }
 
     _handleComment(detail) {
       for (const cb of this._getEventCallbacks(EventType.COMMENT)) {
@@ -181,7 +191,7 @@
           console.error(err);
         }
       }
-    },
+    }
 
     _handleLabelChange(detail) {
       for (const cb of this._getEventCallbacks(EventType.LABEL_CHANGE)) {
@@ -191,7 +201,7 @@
           console.error(err);
         }
       }
-    },
+    }
 
     _handleHighlightjsLoaded(detail) {
       for (const cb of this._getEventCallbacks(EventType.HIGHLIGHTJS_LOADED)) {
@@ -201,7 +211,7 @@
           console.error(err);
         }
       }
-    },
+    }
 
     modifyRevertMsg(change, revertMsg, origMsg) {
       for (const cb of this._getEventCallbacks(EventType.REVERT)) {
@@ -212,7 +222,7 @@
         }
       }
       return revertMsg;
-    },
+    }
 
     modifyRevertSubmissionMsg(change, revertSubmissionMsg, origMsg) {
       for (const cb of this._getEventCallbacks(EventType.REVERT_SUBMISSION)) {
@@ -223,7 +233,7 @@
         }
       }
       return revertSubmissionMsg;
-    },
+    }
 
     getDiffLayers(path, changeNum, patchNum) {
       const layers = [];
@@ -237,7 +247,7 @@
         }
       }
       return layers;
-    },
+    }
 
     /**
      * Retrieves coverage data possibly provided by a plugin.
@@ -265,7 +275,7 @@
         }
         return [];
       });
-    },
+    }
 
     getAdminMenuLinks() {
       const links = [];
@@ -274,7 +284,7 @@
         links.push(...adminApi.getMenuLinks());
       }
       return links;
-    },
+    }
 
     getLabelValuesPostRevert(change) {
       let labels = {};
@@ -286,10 +296,12 @@
         }
       }
       return labels;
-    },
+    }
 
     _getEventCallbacks(type) {
       return this._eventCallbacks[type] || [];
-    },
-  });
+    }
+  }
+
+  customElements.define(GrJsApiInterface.is, GrJsApiInterface);
 })();
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 ae12940..537e55b 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
@@ -18,7 +18,7 @@
 
 <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>
diff --git a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-action-context_test.html b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-action-context_test.html
index 6da117f..f7b7cf8 100644
--- a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-action-context_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-action-context_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-plugin-action-context</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>
diff --git a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-endpoints_test.html b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-endpoints_test.html
index 8ed7f14..b39ed6d 100644
--- a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-endpoints_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-endpoints_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-plugin-endpoints</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>
diff --git a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-loader_test.html b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-loader_test.html
index b0a8c4a..8c1ec96 100644
--- a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-loader_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-loader_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-plugin-host</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>
diff --git a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-rest-api_test.html b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-rest-api_test.html
index bcbd961..4aae032 100644
--- a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-rest-api_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-rest-api_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-plugin-rest-api</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>
diff --git a/polygerrit-ui/app/elements/shared/gr-label-info/gr-label-info.js b/polygerrit-ui/app/elements/shared/gr-label-info/gr-label-info.js
index 3c27c94..0e529b1 100644
--- a/polygerrit-ui/app/elements/shared/gr-label-info/gr-label-info.js
+++ b/polygerrit-ui/app/elements/shared/gr-label-info/gr-label-info.js
@@ -17,17 +17,21 @@
 (function() {
   'use strict';
 
-  Polymer({
-    is: 'gr-label-info',
+  class GrLabelInfo extends Polymer.GestureEventListeners(
+      Polymer.LegacyElementMixin(
+          Polymer.Element)) {
+    static get is() { return 'gr-label-info'; }
 
-    properties: {
-      labelInfo: Object,
-      label: String,
-      /** @type {?} */
-      change: Object,
-      account: Object,
-      mutable: Boolean,
-    },
+    static get properties() {
+      return {
+        labelInfo: Object,
+        label: String,
+        /** @type {?} */
+        change: Object,
+        account: Object,
+        mutable: Boolean,
+      };
+    }
 
     /**
      * @param {!Object} labelInfo
@@ -85,7 +89,7 @@
         }
       }
       return result;
-    },
+    }
 
     /**
      * A user is able to delete a vote iff the mutable property is true and the
@@ -106,7 +110,7 @@
         return '';
       }
       return 'hidden';
-    },
+    }
 
     /**
      * Closure annotation for Polymer.prototype.splice is off.
@@ -133,14 +137,14 @@
                 target.disabled = false;
                 return;
               });
-    },
+    }
 
     _computeValueTooltip(labelInfo, score) {
       if (!labelInfo || !labelInfo.values || !labelInfo.values[score]) {
         return '';
       }
       return labelInfo.values[score];
-    },
+    }
 
     /**
      * @param {!Object} labelInfo
@@ -156,6 +160,8 @@
         }
       }
       return '';
-    },
-  });
+    }
+  }
+
+  customElements.define(GrLabelInfo.is, GrLabelInfo);
 })();
diff --git a/polygerrit-ui/app/elements/shared/gr-label-info/gr-label-info_test.html b/polygerrit-ui/app/elements/shared/gr-label-info/gr-label-info_test.html
index 35dc772..adf88d9 100644
--- a/polygerrit-ui/app/elements/shared/gr-label-info/gr-label-info_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-label-info/gr-label-info_test.html
@@ -17,7 +17,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-label-info</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>
diff --git a/polygerrit-ui/app/elements/shared/gr-label/gr-label.js b/polygerrit-ui/app/elements/shared/gr-label/gr-label.js
index 0de0881..7d82c9c9 100644
--- a/polygerrit-ui/app/elements/shared/gr-label/gr-label.js
+++ b/polygerrit-ui/app/elements/shared/gr-label/gr-label.js
@@ -17,11 +17,16 @@
 (function() {
   'use strict';
 
-  Polymer({
-    is: 'gr-label',
+  /**
+    * @appliesMixin Gerrit.TooltipMixin
+    */
+  class GrLabel extends Polymer.mixinBehaviors( [
+    Gerrit.TooltipBehavior,
+  ], Polymer.GestureEventListeners(
+      Polymer.LegacyElementMixin(
+          Polymer.Element))) {
+    static get is() { return 'gr-label'; }
+  }
 
-    behaviors: [
-      Gerrit.TooltipBehavior,
-    ],
-  });
+  customElements.define(GrLabel.is, GrLabel);
 })();
diff --git a/polygerrit-ui/app/elements/shared/gr-labeled-autocomplete/gr-labeled-autocomplete.js b/polygerrit-ui/app/elements/shared/gr-labeled-autocomplete/gr-labeled-autocomplete.js
index fd0f228..d2646af 100644
--- a/polygerrit-ui/app/elements/shared/gr-labeled-autocomplete/gr-labeled-autocomplete.js
+++ b/polygerrit-ui/app/elements/shared/gr-labeled-autocomplete/gr-labeled-autocomplete.js
@@ -17,60 +17,65 @@
 (function() {
   'use strict';
 
-  Polymer({
-    is: 'gr-labeled-autocomplete',
-
+  class GrLabeledAutocomplete extends Polymer.GestureEventListeners(
+      Polymer.LegacyElementMixin(
+          Polymer.Element)) {
+    static get is() { return 'gr-labeled-autocomplete'; }
     /**
      * Fired when a value is chosen.
      *
      * @event commit
      */
 
-    properties: {
+    static get properties() {
+      return {
 
-      /**
+        /**
        * Used just like the query property of gr-autocomplete.
        *
        * @type {function(string): Promise<?>}
        */
-      query: {
-        type: Function,
-        value() {
-          return function() {
-            return Promise.resolve([]);
-          };
+        query: {
+          type: Function,
+          value() {
+            return function() {
+              return Promise.resolve([]);
+            };
+          },
         },
-      },
 
-      text: {
-        type: String,
-        value: '',
-        notify: true,
-      },
-      label: String,
-      placeholder: String,
-      disabled: Boolean,
+        text: {
+          type: String,
+          value: '',
+          notify: true,
+        },
+        label: String,
+        placeholder: String,
+        disabled: Boolean,
 
-      _autocompleteThreshold: {
-        type: Number,
-        value: 0,
-        readOnly: true,
-      },
-    },
+        _autocompleteThreshold: {
+          type: Number,
+          value: 0,
+          readOnly: true,
+        },
+      };
+    }
 
     _handleTriggerClick(e) {
       // Stop propagation here so we don't confuse gr-autocomplete, which
       // listens for taps on body to try to determine when it's blurred.
       e.stopPropagation();
       this.$.autocomplete.focus();
-    },
+    }
 
     setText(text) {
       this.$.autocomplete.setText(text);
-    },
+    }
 
     clear() {
       this.setText('');
-    },
-  });
+    }
+  }
+
+  customElements.define(GrLabeledAutocomplete.is, GrLabeledAutocomplete);
 })();
diff --git a/polygerrit-ui/app/elements/shared/gr-labeled-autocomplete/gr-labeled-autocomplete_test.html b/polygerrit-ui/app/elements/shared/gr-labeled-autocomplete/gr-labeled-autocomplete_test.html
index b257746..7e8f73f 100644
--- a/polygerrit-ui/app/elements/shared/gr-labeled-autocomplete/gr-labeled-autocomplete_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-labeled-autocomplete/gr-labeled-autocomplete_test.html
@@ -17,7 +17,7 @@
 -->
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-labeled-autocomplete</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>
diff --git a/polygerrit-ui/app/elements/shared/gr-lib-loader/gr-lib-loader.js b/polygerrit-ui/app/elements/shared/gr-lib-loader/gr-lib-loader.js
index 260c819..1fd122c 100644
--- a/polygerrit-ui/app/elements/shared/gr-lib-loader/gr-lib-loader.js
+++ b/polygerrit-ui/app/elements/shared/gr-lib-loader/gr-lib-loader.js
@@ -20,21 +20,25 @@
   const HLJS_PATH = 'bower_components/highlightjs/highlight.min.js';
   const DARK_THEME_PATH = 'styles/themes/dark-theme.html';
 
-  Polymer({
-    is: 'gr-lib-loader',
+  class GrLibLoader extends Polymer.GestureEventListeners(
+      Polymer.LegacyElementMixin(
+          Polymer.Element)) {
+    static get is() { return 'gr-lib-loader'; }
 
-    properties: {
-      _hljsState: {
-        type: Object,
+    static get properties() {
+      return {
+        _hljsState: {
+          type: Object,
 
-        // NOTE: intended singleton.
-        value: {
-          configured: false,
-          loading: false,
-          callbacks: [],
+          // NOTE: intended singleton.
+          value: {
+            configured: false,
+            loading: false,
+            callbacks: [],
+          },
         },
-      },
-    },
+      };
+    }
 
     /**
      * Get the HLJS library. Returns a promise that resolves with a reference to
@@ -59,7 +63,7 @@
 
         this._hljsState.callbacks.push(resolve);
       });
-    },
+    }
 
     /**
      * Loads the dark theme document. Returns a promise that resolves with a
@@ -79,7 +83,7 @@
               resolve(cs);
             });
       });
-    },
+    }
 
     /**
      * Execute callbacks awaiting the HLJS lib load.
@@ -94,7 +98,7 @@
         cb(lib);
       }
       this._hljsState.callbacks = [];
-    },
+    }
 
     /**
      * Get the HLJS library, assuming it has been loaded. Configure the library
@@ -109,7 +113,7 @@
         lib.configure({classPrefix: 'gr-diff gr-syntax gr-syntax-'});
       }
       return lib;
-    },
+    }
 
     /**
      * Get the resource path used to load the application. If the application
@@ -121,7 +125,7 @@
         return window.STATIC_RESOURCE_PATH + '/';
       }
       return '/';
-    },
+    }
 
     /**
      * Load and execute a JS file from the lib root.
@@ -143,12 +147,14 @@
         script.onerror = reject;
         Polymer.dom(document.head).appendChild(script);
       });
-    },
+    }
 
     _getHLJSUrl() {
       const root = this._getLibRoot();
       if (!root) { return null; }
       return root + HLJS_PATH;
-    },
-  });
+    }
+  }
+
+  customElements.define(GrLibLoader.is, GrLibLoader);
 })();
diff --git a/polygerrit-ui/app/elements/shared/gr-lib-loader/gr-lib-loader_test.html b/polygerrit-ui/app/elements/shared/gr-lib-loader/gr-lib-loader_test.html
index 10d1608..832a558 100644
--- a/polygerrit-ui/app/elements/shared/gr-lib-loader/gr-lib-loader_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-lib-loader/gr-lib-loader_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-lib-loader</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>
diff --git a/polygerrit-ui/app/elements/shared/gr-limited-text/gr-limited-text.js b/polygerrit-ui/app/elements/shared/gr-limited-text/gr-limited-text.js
index 048e4f5..9239866 100644
--- a/polygerrit-ui/app/elements/shared/gr-limited-text/gr-limited-text.js
+++ b/polygerrit-ui/app/elements/shared/gr-limited-text/gr-limited-text.js
@@ -17,57 +17,63 @@
 (function() {
   'use strict';
 
+  /**
+    * @appliesMixin Gerrit.TooltipMixin
+    */
   /*
    * The gr-limited-text element is for displaying text with a maximum length
    * (in number of characters) to display. If the length of the text exceeds the
    * configured limit, then an ellipsis indicates that the text was truncated
    * and a tooltip containing the full text is enabled.
    */
+  class GrLimitedText extends Polymer.mixinBehaviors( [
+    Gerrit.TooltipBehavior,
+  ], Polymer.GestureEventListeners(
+      Polymer.LegacyElementMixin(
+          Polymer.Element))) {
+    static get is() { return 'gr-limited-text'; }
 
-  Polymer({
-    is: 'gr-limited-text',
-
-    properties: {
+    static get properties() {
+      return {
       /** The un-truncated text to display. */
-      text: String,
+        text: String,
 
-      /** The maximum length for the text to display before truncating. */
-      limit: {
-        type: Number,
-        value: null,
-      },
+        /** The maximum length for the text to display before truncating. */
+        limit: {
+          type: Number,
+          value: null,
+        },
 
-      /** Boolean property used by Gerrit.TooltipBehavior. */
-      hasTooltip: {
-        type: Boolean,
-        value: false,
-      },
+        /** Boolean property used by Gerrit.TooltipBehavior. */
+        hasTooltip: {
+          type: Boolean,
+          value: false,
+        },
 
-      /**
+        /**
        * Disable the tooltip.
        * When set to true, will not show tooltip even text is over limit
        */
-      disableTooltip: {
-        type: Boolean,
-        value: false,
-      },
+        disableTooltip: {
+          type: Boolean,
+          value: false,
+        },
 
-      /**
+        /**
        * The maximum number of characters to display in the tooltop.
        */
-      tooltipLimit: {
-        type: Number,
-        value: 1024,
-      },
-    },
+        tooltipLimit: {
+          type: Number,
+          value: 1024,
+        },
+      };
+    }
 
-    observers: [
-      '_updateTitle(text, limit, tooltipLimit)',
-    ],
-
-    behaviors: [
-      Gerrit.TooltipBehavior,
-    ],
+    static get observers() {
+      return [
+        '_updateTitle(text, limit, tooltipLimit)',
+      ];
+    }
 
     /**
      * The text or limit have changed. Recompute whether a tooltip needs to be
@@ -85,13 +91,15 @@
       } else {
         this.removeAttribute('title');
       }
-    },
+    }
 
     _computeDisplayText(text, limit) {
       if (!!limit && !!text && text.length > limit) {
         return text.substr(0, limit - 1) + '…';
       }
       return text;
-    },
-  });
+    }
+  }
+
+  customElements.define(GrLimitedText.is, GrLimitedText);
 })();
diff --git a/polygerrit-ui/app/elements/shared/gr-limited-text/gr-limited-text_test.html b/polygerrit-ui/app/elements/shared/gr-limited-text/gr-limited-text_test.html
index 7946bb6..3ba30d1 100644
--- a/polygerrit-ui/app/elements/shared/gr-limited-text/gr-limited-text_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-limited-text/gr-limited-text_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-limited-text</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>
diff --git a/polygerrit-ui/app/elements/shared/gr-linked-chip/gr-linked-chip.js b/polygerrit-ui/app/elements/shared/gr-linked-chip/gr-linked-chip.js
index 33a9c25..aaae3d1 100644
--- a/polygerrit-ui/app/elements/shared/gr-linked-chip/gr-linked-chip.js
+++ b/polygerrit-ui/app/elements/shared/gr-linked-chip/gr-linked-chip.js
@@ -17,41 +17,48 @@
 (function() {
   'use strict';
 
-  Polymer({
-    is: 'gr-linked-chip',
+  /**
+    * @appliesMixin Gerrit.FireMixin
+    */
+  class GrLinkedChip extends Polymer.mixinBehaviors( [
+    Gerrit.FireBehavior,
+  ], Polymer.GestureEventListeners(
+      Polymer.LegacyElementMixin(
+          Polymer.Element))) {
+    static get is() { return 'gr-linked-chip'; }
 
-    properties: {
-      href: String,
-      disabled: {
-        type: Boolean,
-        value: false,
-        reflectToAttribute: true,
-      },
-      removable: {
-        type: Boolean,
-        value: false,
-      },
-      text: String,
-      transparentBackground: {
-        type: Boolean,
-        value: false,
-      },
+    static get properties() {
+      return {
+        href: String,
+        disabled: {
+          type: Boolean,
+          value: false,
+          reflectToAttribute: true,
+        },
+        removable: {
+          type: Boolean,
+          value: false,
+        },
+        text: String,
+        transparentBackground: {
+          type: Boolean,
+          value: false,
+        },
 
-      /**  If provided, sets the maximum length of the content. */
-      limit: Number,
-    },
-
-    behaviors: [
-      Gerrit.FireBehavior,
-    ],
+        /**  If provided, sets the maximum length of the content. */
+        limit: Number,
+      };
+    }
 
     _getBackgroundClass(transparent) {
       return transparent ? 'transparentBackground' : '';
-    },
+    }
 
     _handleRemoveTap(e) {
       e.preventDefault();
       this.fire('remove');
-    },
-  });
+    }
+  }
+
+  customElements.define(GrLinkedChip.is, GrLinkedChip);
 })();
diff --git a/polygerrit-ui/app/elements/shared/gr-linked-chip/gr-linked-chip_test.html b/polygerrit-ui/app/elements/shared/gr-linked-chip/gr-linked-chip_test.html
index 22a2eaf..733e897 100644
--- a/polygerrit-ui/app/elements/shared/gr-linked-chip/gr-linked-chip_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-linked-chip/gr-linked-chip_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-linked-chip</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>
diff --git a/polygerrit-ui/app/elements/shared/gr-linked-text/gr-linked-text.js b/polygerrit-ui/app/elements/shared/gr-linked-text/gr-linked-text.js
index e7247b9..25f406e 100644
--- a/polygerrit-ui/app/elements/shared/gr-linked-text/gr-linked-text.js
+++ b/polygerrit-ui/app/elements/shared/gr-linked-text/gr-linked-text.js
@@ -17,31 +17,37 @@
 (function() {
   'use strict';
 
-  Polymer({
-    is: 'gr-linked-text',
+  class GrLinkedText extends Polymer.GestureEventListeners(
+      Polymer.LegacyElementMixin(
+          Polymer.Element)) {
+    static get is() { return 'gr-linked-text'; }
 
-    properties: {
-      removeZeroWidthSpace: Boolean,
-      content: {
-        type: String,
-        observer: '_contentChanged',
-      },
-      pre: {
-        type: Boolean,
-        value: false,
-        reflectToAttribute: true,
-      },
-      disabled: {
-        type: Boolean,
-        value: false,
-        reflectToAttribute: true,
-      },
-      config: Object,
-    },
+    static get properties() {
+      return {
+        removeZeroWidthSpace: Boolean,
+        content: {
+          type: String,
+          observer: '_contentChanged',
+        },
+        pre: {
+          type: Boolean,
+          value: false,
+          reflectToAttribute: true,
+        },
+        disabled: {
+          type: Boolean,
+          value: false,
+          reflectToAttribute: true,
+        },
+        config: Object,
+      };
+    }
 
-    observers: [
-      '_contentOrConfigChanged(content, config)',
-    ],
+    static get observers() {
+      return [
+        '_contentOrConfigChanged(content, config)',
+      ];
+    }
 
     _contentChanged(content) {
       // In the case where the config may not be set (perhaps due to the
@@ -49,7 +55,7 @@
       // prevent waiting on the config to display the text.
       if (this.config != null) { return; }
       this.$.output.textContent = content;
-    },
+    }
 
     /**
      * Because either the source text or the linkification config has changed,
@@ -74,7 +80,7 @@
         anchor.setAttribute('target', '_blank');
         anchor.setAttribute('rel', 'noopener');
       });
-    },
+    }
 
     /**
      * This method is called when the GrLikTextParser emits a partial result
@@ -100,6 +106,8 @@
       } else if (fragment) {
         output.appendChild(fragment);
       }
-    },
-  });
+    }
+  }
+
+  customElements.define(GrLinkedText.is, GrLinkedText);
 })();
diff --git a/polygerrit-ui/app/elements/shared/gr-linked-text/gr-linked-text_test.html b/polygerrit-ui/app/elements/shared/gr-linked-text/gr-linked-text_test.html
index f01a75c..e9477bf 100644
--- a/polygerrit-ui/app/elements/shared/gr-linked-text/gr-linked-text_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-linked-text/gr-linked-text_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-linked-text</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>
diff --git a/polygerrit-ui/app/elements/shared/gr-list-view/gr-list-view.js b/polygerrit-ui/app/elements/shared/gr-list-view/gr-list-view.js
index 8d81030..9d5d8f5 100644
--- a/polygerrit-ui/app/elements/shared/gr-list-view/gr-list-view.js
+++ b/polygerrit-ui/app/elements/shared/gr-list-view/gr-list-view.js
@@ -19,31 +19,39 @@
 
   const REQUEST_DEBOUNCE_INTERVAL_MS = 200;
 
-  Polymer({
-    is: 'gr-list-view',
+  /**
+    * @appliesMixin Gerrit.BaseUrlMixin
+    * @appliesMixin Gerrit.FireMixin
+    * @appliesMixin Gerrit.URLEncodingMixin
+    */
+  class GrListView extends Polymer.mixinBehaviors( [
+    Gerrit.BaseUrlBehavior,
+    Gerrit.FireBehavior,
+    Gerrit.URLEncodingBehavior,
+  ], Polymer.GestureEventListeners(
+      Polymer.LegacyElementMixin(
+          Polymer.Element))) {
+    static get is() { return 'gr-list-view'; }
 
-    properties: {
-      createNew: Boolean,
-      items: Array,
-      itemsPerPage: Number,
-      filter: {
-        type: String,
-        observer: '_filterChanged',
-      },
-      offset: Number,
-      loading: Boolean,
-      path: String,
-    },
-
-    behaviors: [
-      Gerrit.BaseUrlBehavior,
-      Gerrit.FireBehavior,
-      Gerrit.URLEncodingBehavior,
-    ],
+    static get properties() {
+      return {
+        createNew: Boolean,
+        items: Array,
+        itemsPerPage: Number,
+        filter: {
+          type: String,
+          observer: '_filterChanged',
+        },
+        offset: Number,
+        loading: Boolean,
+        path: String,
+      };
+    }
 
     detached() {
+      super.detached();
       this.cancelDebouncer('reload');
-    },
+    }
 
     _filterChanged(newFilter, oldFilter) {
       if (!newFilter && !oldFilter) {
@@ -51,7 +59,7 @@
       }
 
       this._debounceReload(newFilter);
-    },
+    }
 
     _debounceReload(filter) {
       this.debounce('reload', () => {
@@ -61,11 +69,11 @@
         }
         page.show(this.path);
       }, REQUEST_DEBOUNCE_INTERVAL_MS);
-    },
+    }
 
     _createNewItem() {
       this.fire('create-clicked');
-    },
+    }
 
     _computeNavLink(offset, direction, itemsPerPage, filter, path) {
       // Offset could be a string when passed from the router.
@@ -79,15 +87,15 @@
         href += ',' + newOffset;
       }
       return href;
-    },
+    }
 
     _computeCreateClass(createNew) {
       return createNew ? 'show' : '';
-    },
+    }
 
     _hidePrevArrow(loading, offset) {
       return loading || offset === 0;
-    },
+    }
 
     _hideNextArrow(loading, items) {
       if (loading || !items || !items.length) {
@@ -95,6 +103,8 @@
       }
       const lastPage = items.length < this.itemsPerPage + 1;
       return lastPage;
-    },
-  });
+    }
+  }
+
+  customElements.define(GrListView.is, GrListView);
 })();
diff --git a/polygerrit-ui/app/elements/shared/gr-list-view/gr-list-view_test.html b/polygerrit-ui/app/elements/shared/gr-list-view/gr-list-view_test.html
index c67d8b2..8f87b38 100644
--- a/polygerrit-ui/app/elements/shared/gr-list-view/gr-list-view_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-list-view/gr-list-view_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-list-view</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 <script src="/bower_components/page/page.js"></script>
 <script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
diff --git a/polygerrit-ui/app/elements/shared/gr-overlay/gr-overlay.js b/polygerrit-ui/app/elements/shared/gr-overlay/gr-overlay.js
index 8623458..9c7e680 100644
--- a/polygerrit-ui/app/elements/shared/gr-overlay/gr-overlay.js
+++ b/polygerrit-ui/app/elements/shared/gr-overlay/gr-overlay.js
@@ -21,9 +21,16 @@
   const AWAIT_STEP = 5;
   const BREAKPOINT_FULLSCREEN_OVERLAY = '50em';
 
-  Polymer({
-    is: 'gr-overlay',
-
+  /**
+    * @appliesMixin Gerrit.FireMixin
+    */
+  class GrOverlay extends Polymer.mixinBehaviors( [
+    Gerrit.FireBehavior,
+    Polymer.IronOverlayBehavior,
+  ], Polymer.GestureEventListeners(
+      Polymer.LegacyElementMixin(
+          Polymer.Element))) {
+    static get is() { return 'gr-overlay'; }
     /**
      * Fired when a fullscreen overlay is closed
      *
@@ -36,22 +43,22 @@
      * @event fullscreen-overlay-opened
      */
 
-    properties: {
-      _fullScreenOpen: {
-        type: Boolean,
-        value: false,
-      },
-    },
+    static get properties() {
+      return {
+        _fullScreenOpen: {
+          type: Boolean,
+          value: false,
+        },
+      };
+    }
 
-    behaviors: [
-      Gerrit.FireBehavior,
-      Polymer.IronOverlayBehavior,
-    ],
-
-    listeners: {
-      'iron-overlay-closed': '_close',
-      'iron-overlay-cancelled': '_close',
-    },
+    created() {
+      super.created();
+      this.addEventListener('iron-overlay-closed',
+          () => this._close());
+      this.addEventListener('iron-overlay-cancelled',
+          () => this._close());
+    }
 
     open(...args) {
       return new Promise(resolve => {
@@ -62,18 +69,18 @@
         }
         this._awaitOpen(resolve);
       });
-    },
+    }
 
     _isMobile() {
       return window.matchMedia(`(max-width: ${BREAKPOINT_FULLSCREEN_OVERLAY})`);
-    },
+    }
 
     _close() {
       if (this._fullScreenOpen) {
         this.fire('fullscreen-overlay-closed');
         this._fullScreenOpen = false;
       }
-    },
+    }
 
     /**
      * Override the focus stops that iron-overlay-behavior tries to find.
@@ -81,7 +88,7 @@
     setFocusStops(stops) {
       this.__firstFocusableNode = stops.start;
       this.__lastFocusableNode = stops.end;
-    },
+    }
 
     /**
      * NOTE: (wyatta) Slightly hacky way to listen to the overlay actually
@@ -99,10 +106,12 @@
         }, AWAIT_STEP);
       };
       step.call(this);
-    },
+    }
 
     _id() {
       return this.getAttribute('id') || 'global';
-    },
-  });
+    }
+  }
+
+  customElements.define(GrOverlay.is, GrOverlay);
 })();
diff --git a/polygerrit-ui/app/elements/shared/gr-overlay/gr-overlay_test.html b/polygerrit-ui/app/elements/shared/gr-overlay/gr-overlay_test.html
index 08b7497..659823f 100644
--- a/polygerrit-ui/app/elements/shared/gr-overlay/gr-overlay_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-overlay/gr-overlay_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-overlay</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/page/page.js"></script>
diff --git a/polygerrit-ui/app/elements/shared/gr-page-nav/gr-page-nav.js b/polygerrit-ui/app/elements/shared/gr-page-nav/gr-page-nav.js
index 2e05607..23389ee 100644
--- a/polygerrit-ui/app/elements/shared/gr-page-nav/gr-page-nav.js
+++ b/polygerrit-ui/app/elements/shared/gr-page-nav/gr-page-nav.js
@@ -17,20 +17,26 @@
 (function() {
   'use strict';
 
-  Polymer({
-    is: 'gr-page-nav',
+  class GrPageNav extends Polymer.GestureEventListeners(
+      Polymer.LegacyElementMixin(
+          Polymer.Element)) {
+    static get is() { return 'gr-page-nav'; }
 
-    properties: {
-      _headerHeight: Number,
-    },
+    static get properties() {
+      return {
+        _headerHeight: Number,
+      };
+    }
 
     attached() {
+      super.attached();
       this.listen(window, 'scroll', '_handleBodyScroll');
-    },
+    }
 
     detached() {
+      super.detached();
       this.unlisten(window, 'scroll', '_handleBodyScroll');
-    },
+    }
 
     _handleBodyScroll() {
       if (this._headerHeight === undefined) {
@@ -45,20 +51,22 @@
 
       this.$.nav.classList.toggle('pinned',
           this._getScrollY() >= this._headerHeight);
-    },
+    }
 
     /* Functions used for test purposes */
     _getOffsetParent(element) {
       if (!element || !element.offsetParent) { return ''; }
       return element.offsetParent;
-    },
+    }
 
     _getOffsetTop(element) {
       return element.offsetTop;
-    },
+    }
 
     _getScrollY() {
       return window.scrollY;
-    },
-  });
+    }
+  }
+
+  customElements.define(GrPageNav.is, GrPageNav);
 })();
diff --git a/polygerrit-ui/app/elements/shared/gr-page-nav/gr-page-nav_test.html b/polygerrit-ui/app/elements/shared/gr-page-nav/gr-page-nav_test.html
index b384b47..663d3bd 100644
--- a/polygerrit-ui/app/elements/shared/gr-page-nav/gr-page-nav_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-page-nav/gr-page-nav_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-page-nav</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/page/page.js"></script>
diff --git a/polygerrit-ui/app/elements/shared/gr-repo-branch-picker/gr-repo-branch-picker.js b/polygerrit-ui/app/elements/shared/gr-repo-branch-picker/gr-repo-branch-picker.js
index e2298c3..47979f1 100644
--- a/polygerrit-ui/app/elements/shared/gr-repo-branch-picker/gr-repo-branch-picker.js
+++ b/polygerrit-ui/app/elements/shared/gr-repo-branch-picker/gr-repo-branch-picker.js
@@ -20,47 +20,54 @@
   const SUGGESTIONS_LIMIT = 15;
   const REF_PREFIX = 'refs/heads/';
 
-  Polymer({
-    is: 'gr-repo-branch-picker',
+  /**
+    * @appliesMixin Gerrit.URLEncodingMixin
+    */
+  class GrRepoBranchPicker extends Polymer.mixinBehaviors( [
+    Gerrit.URLEncodingBehavior,
+  ], Polymer.GestureEventListeners(
+      Polymer.LegacyElementMixin(
+          Polymer.Element))) {
+    static get is() { return 'gr-repo-branch-picker'; }
 
-    properties: {
-      repo: {
-        type: String,
-        notify: true,
-        observer: '_repoChanged',
-      },
-      branch: {
-        type: String,
-        notify: true,
-      },
-      _branchDisabled: Boolean,
-      _query: {
-        type: Function,
-        value() {
-          return this._getRepoBranchesSuggestions.bind(this);
+    static get properties() {
+      return {
+        repo: {
+          type: String,
+          notify: true,
+          observer: '_repoChanged',
         },
-      },
-      _repoQuery: {
-        type: Function,
-        value() {
-          return this._getRepoSuggestions.bind(this);
+        branch: {
+          type: String,
+          notify: true,
         },
-      },
-    },
-
-    behaviors: [
-      Gerrit.URLEncodingBehavior,
-    ],
+        _branchDisabled: Boolean,
+        _query: {
+          type: Function,
+          value() {
+            return this._getRepoBranchesSuggestions.bind(this);
+          },
+        },
+        _repoQuery: {
+          type: Function,
+          value() {
+            return this._getRepoSuggestions.bind(this);
+          },
+        },
+      };
+    }
 
     attached() {
+      super.attached();
       if (this.repo) {
         this.$.repoInput.setText(this.repo);
       }
-    },
+    }
 
     ready() {
+      super.ready();
       this._branchDisabled = !this.repo;
-    },
+    }
 
     _getRepoBranchesSuggestions(input) {
       if (!this.repo) { return Promise.resolve([]); }
@@ -69,19 +76,19 @@
       }
       return this.$.restAPI.getRepoBranches(input, this.repo, SUGGESTIONS_LIMIT)
           .then(this._branchResponseToSuggestions.bind(this));
-    },
+    }
 
     _getRepoSuggestions(input) {
       return this.$.restAPI.getRepos(input, SUGGESTIONS_LIMIT)
           .then(this._repoResponseToSuggestions.bind(this));
-    },
+    }
 
     _repoResponseToSuggestions(res) {
       return res.map(repo => ({
         name: repo.name,
         value: this.singleDecodeURL(repo.id),
       }));
-    },
+    }
 
     _branchResponseToSuggestions(res) {
       return Object.keys(res).map(key => {
@@ -91,19 +98,21 @@
         }
         return {name: branch, value: branch};
       });
-    },
+    }
 
     _repoCommitted(e) {
       this.repo = e.detail.value;
-    },
+    }
 
     _branchCommitted(e) {
       this.branch = e.detail.value;
-    },
+    }
 
     _repoChanged() {
       this.$.branchInput.clear();
       this._branchDisabled = !this.repo;
-    },
-  });
+    }
+  }
+
+  customElements.define(GrRepoBranchPicker.is, GrRepoBranchPicker);
 })();
diff --git a/polygerrit-ui/app/elements/shared/gr-repo-branch-picker/gr-repo-branch-picker_test.html b/polygerrit-ui/app/elements/shared/gr-repo-branch-picker/gr-repo-branch-picker_test.html
index 1ed9151..e7252d6 100644
--- a/polygerrit-ui/app/elements/shared/gr-repo-branch-picker/gr-repo-branch-picker_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-repo-branch-picker/gr-repo-branch-picker_test.html
@@ -17,7 +17,7 @@
 -->
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-repo-branch-picker</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>
diff --git a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-auth_test.html b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-auth_test.html
index dc07d0f..6c5c150 100644
--- a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-auth_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-auth_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-auth</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>
diff --git a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-etag-decorator_test.html b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-etag-decorator_test.html
index 76c8c2c..623ea18 100644
--- a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-etag-decorator_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-etag-decorator_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-etag-decorator</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>
diff --git a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface.js b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface.js
index 043a5ed..eae25ae 100644
--- a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface.js
+++ b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface.js
@@ -39,16 +39,21 @@
   const ANONYMIZED_REVISION_BASE_URL = ANONYMIZED_CHANGE_BASE_URL +
       '/revisions/*';
 
-  Polymer({
-    is: 'gr-rest-api-interface',
-
-    behaviors: [
-      Gerrit.FireBehavior,
-      Gerrit.PathListBehavior,
-      Gerrit.PatchSetBehavior,
-      Gerrit.RESTClientBehavior,
-    ],
-
+  /**
+    * @appliesMixin Gerrit.FireMixin
+    * @appliesMixin Gerrit.PathListMixin
+    * @appliesMixin Gerrit.PatchSetMixin
+    * @appliesMixin Gerrit.RESTClientMixin
+    */
+  class GrRestApiInterface extends Polymer.mixinBehaviors( [
+    Gerrit.FireBehavior,
+    Gerrit.PathListBehavior,
+    Gerrit.PatchSetBehavior,
+    Gerrit.RESTClientBehavior,
+  ], Polymer.GestureEventListeners(
+      Polymer.LegacyElementMixin(
+          Polymer.Element))) {
+    static get is() { return 'gr-rest-api-interface'; }
     /**
      * Fired when an server error occurs.
      *
@@ -73,43 +78,49 @@
      * @event rpc-log
      */
 
-    properties: {
-      _cache: {
-        type: Object,
-        value: new SiteBasedCache(), // Shared across instances.
-      },
-      _credentialCheck: {
-        type: Object,
-        value: {checking: false}, // Shared across instances.
-      },
-      _sharedFetchPromises: {
-        type: Object,
-        value: new FetchPromisesCache(), // Shared across instances.
-      },
-      _pendingRequests: {
-        type: Object,
-        value: {}, // Intentional to share the object across instances.
-      },
-      _etags: {
-        type: Object,
-        value: new GrEtagDecorator(), // Share across instances.
-      },
-      /**
+    constructor() {
+      super();
+      this.JSON_PREFIX = JSON_PREFIX;
+    }
+
+    static get properties() {
+      return {
+        _cache: {
+          type: Object,
+          value: new SiteBasedCache(), // Shared across instances.
+        },
+        _credentialCheck: {
+          type: Object,
+          value: {checking: false}, // Shared across instances.
+        },
+        _sharedFetchPromises: {
+          type: Object,
+          value: new FetchPromisesCache(), // Shared across instances.
+        },
+        _pendingRequests: {
+          type: Object,
+          value: {}, // Intentional to share the object across instances.
+        },
+        _etags: {
+          type: Object,
+          value: new GrEtagDecorator(), // Share across instances.
+        },
+        /**
        * Used to maintain a mapping of changeNums to project names.
        */
-      _projectLookup: {
-        type: Object,
-        value: {}, // Intentional to share the object across instances.
-      },
-      _auth: {
-        type: Object,
-        value: Gerrit.Auth, // Share across instances.
-      },
-    },
-
-    JSON_PREFIX,
+        _projectLookup: {
+          type: Object,
+          value: {}, // Intentional to share the object across instances.
+        },
+        _auth: {
+          type: Object,
+          value: Gerrit.Auth, // Share across instances.
+        },
+      };
+    }
 
     created() {
+      super.created();
       /* Polymer 1 and Polymer 2 have slightly different lifecycle.
       * Differences are not very well documented (see
       * https://github.com/Polymer/old-docs-site/issues/2322).
@@ -130,12 +141,13 @@
       //
 
       this._initRestApiHelper();
-    },
+    }
 
     ready() {
+      super.ready();
       // See comments in created()
       this._initRestApiHelper();
-    },
+    }
 
     _initRestApiHelper() {
       if (this._restApiHelper) {
@@ -146,12 +158,12 @@
         this._restApiHelper = new GrRestApiHelper(this._cache, this._auth,
             this._sharedFetchPromises, this._credentialCheck, this);
       }
-    },
+    }
 
     _fetchSharedCacheURL(req) {
       // Cache is shared across instances
       return this._restApiHelper.fetchCacheURL(req);
-    },
+    }
 
     /**
      * @param {!Object} response
@@ -159,7 +171,7 @@
      */
     getResponseObject(response) {
       return this._restApiHelper.getResponseObject(response);
-    },
+    }
 
     getConfig(noCache) {
       if (!noCache) {
@@ -173,7 +185,7 @@
         url: '/config/server/info',
         reportUrlAsIs: true,
       });
-    },
+    }
 
     getRepo(repo, opt_errFn) {
       // TODO(kaspern): Rename rest api from /projects/ to /repos/ once backend
@@ -183,7 +195,7 @@
         errFn: opt_errFn,
         anonymizedUrl: '/projects/*',
       });
-    },
+    }
 
     getProjectConfig(repo, opt_errFn) {
       // TODO(kaspern): Rename rest api from /projects/ to /repos/ once backend
@@ -193,7 +205,7 @@
         errFn: opt_errFn,
         anonymizedUrl: '/projects/*/config',
       });
-    },
+    }
 
     getRepoAccess(repo) {
       // TODO(kaspern): Rename rest api from /projects/ to /repos/ once backend
@@ -202,7 +214,7 @@
         url: '/access/?project=' + encodeURIComponent(repo),
         anonymizedUrl: '/access/?project=*',
       });
-    },
+    }
 
     getRepoDashboards(repo, opt_errFn) {
       // TODO(kaspern): Rename rest api from /projects/ to /repos/ once backend
@@ -212,7 +224,7 @@
         errFn: opt_errFn,
         anonymizedUrl: '/projects/*/dashboards?inherited',
       });
-    },
+    }
 
     saveRepoConfig(repo, config, opt_errFn) {
       // TODO(kaspern): Rename rest api from /projects/ to /repos/ once backend
@@ -226,7 +238,7 @@
         errFn: opt_errFn,
         anonymizedUrl: '/projects/*/config',
       });
-    },
+    }
 
     runRepoGC(repo, opt_errFn) {
       if (!repo) { return ''; }
@@ -240,7 +252,7 @@
         errFn: opt_errFn,
         anonymizedUrl: '/projects/*/gc',
       });
-    },
+    }
 
     /**
      * @param {?Object} config
@@ -258,7 +270,7 @@
         errFn: opt_errFn,
         anonymizedUrl: '/projects/*',
       });
-    },
+    }
 
     /**
      * @param {?Object} config
@@ -274,7 +286,7 @@
         errFn: opt_errFn,
         anonymizedUrl: '/groups/*',
       });
-    },
+    }
 
     getGroupConfig(group, opt_errFn) {
       return this._restApiHelper.fetchJSON({
@@ -282,7 +294,7 @@
         errFn: opt_errFn,
         anonymizedUrl: '/groups/*/detail',
       });
-    },
+    }
 
     /**
      * @param {string} repo
@@ -302,7 +314,7 @@
         errFn: opt_errFn,
         anonymizedUrl: '/projects/*/branches/*',
       });
-    },
+    }
 
     /**
      * @param {string} repo
@@ -322,7 +334,7 @@
         errFn: opt_errFn,
         anonymizedUrl: '/projects/*/tags/*',
       });
-    },
+    }
 
     /**
      * @param {string} name
@@ -343,7 +355,7 @@
         errFn: opt_errFn,
         anonymizedUrl: '/projects/*/branches/*',
       });
-    },
+    }
 
     /**
      * @param {string} name
@@ -364,7 +376,7 @@
         errFn: opt_errFn,
         anonymizedUrl: '/projects/*/tags/*',
       });
-    },
+    }
 
     /**
      * @param {!string} groupName
@@ -378,7 +390,7 @@
       };
       return this._fetchSharedCacheURL(req)
           .then(configs => configs.hasOwnProperty(groupName));
-    },
+    }
 
     getGroupMembers(groupName, opt_errFn) {
       const encodeName = encodeURIComponent(groupName);
@@ -387,14 +399,14 @@
         errFn: opt_errFn,
         anonymizedUrl: '/groups/*/members',
       });
-    },
+    }
 
     getIncludedGroup(groupName) {
       return this._restApiHelper.fetchJSON({
         url: `/groups/${encodeURIComponent(groupName)}/groups/`,
         anonymizedUrl: '/groups/*/groups',
       });
-    },
+    }
 
     saveGroupName(groupId, name) {
       const encodeId = encodeURIComponent(groupId);
@@ -404,7 +416,7 @@
         body: {name},
         anonymizedUrl: '/groups/*/name',
       });
-    },
+    }
 
     saveGroupOwner(groupId, ownerId) {
       const encodeId = encodeURIComponent(groupId);
@@ -414,7 +426,7 @@
         body: {owner: ownerId},
         anonymizedUrl: '/groups/*/owner',
       });
-    },
+    }
 
     saveGroupDescription(groupId, description) {
       const encodeId = encodeURIComponent(groupId);
@@ -424,7 +436,7 @@
         body: {description},
         anonymizedUrl: '/groups/*/description',
       });
-    },
+    }
 
     saveGroupOptions(groupId, options) {
       const encodeId = encodeURIComponent(groupId);
@@ -434,7 +446,7 @@
         body: options,
         anonymizedUrl: '/groups/*/options',
       });
-    },
+    }
 
     getGroupAuditLog(group, opt_errFn) {
       return this._fetchSharedCacheURL({
@@ -442,7 +454,7 @@
         errFn: opt_errFn,
         anonymizedUrl: '/groups/*/log.audit',
       });
-    },
+    }
 
     saveGroupMembers(groupName, groupMembers) {
       const encodeName = encodeURIComponent(groupName);
@@ -453,7 +465,7 @@
         parseResponse: true,
         anonymizedUrl: '/groups/*/members/*',
       });
-    },
+    }
 
     saveIncludedGroup(groupName, includedGroup, opt_errFn) {
       const encodeName = encodeURIComponent(groupName);
@@ -469,7 +481,7 @@
           return this.getResponseObject(response);
         }
       });
-    },
+    }
 
     deleteGroupMembers(groupName, groupMembers) {
       const encodeName = encodeURIComponent(groupName);
@@ -479,7 +491,7 @@
         url: `/groups/${encodeName}/members/${encodeMember}`,
         anonymizedUrl: '/groups/*/members/*',
       });
-    },
+    }
 
     deleteIncludedGroup(groupName, includedGroup) {
       const encodeName = encodeURIComponent(groupName);
@@ -489,14 +501,14 @@
         url: `/groups/${encodeName}/groups/${encodeIncludedGroup}`,
         anonymizedUrl: '/groups/*/groups/*',
       });
-    },
+    }
 
     getVersion() {
       return this._fetchSharedCacheURL({
         url: '/config/server/version',
         reportUrlAsIs: true,
       });
-    },
+    }
 
     getDiffPreferences() {
       return this.getLoggedIn().then(loggedIn => {
@@ -527,7 +539,7 @@
           theme: 'DEFAULT',
         });
       });
-    },
+    }
 
     getEditPreferences() {
       return this.getLoggedIn().then(loggedIn => {
@@ -558,7 +570,7 @@
           theme: 'DEFAULT',
         });
       });
-    },
+    }
 
     /**
      * @param {?Object} prefs
@@ -578,7 +590,7 @@
         errFn: opt_errFn,
         reportUrlAsIs: true,
       });
-    },
+    }
 
     /**
      * @param {?Object} prefs
@@ -594,7 +606,7 @@
         errFn: opt_errFn,
         reportUrlAsIs: true,
       });
-    },
+    }
 
     /**
      * @param {?Object} prefs
@@ -610,7 +622,7 @@
         errFn: opt_errFn,
         reportUrlAsIs: true,
       });
-    },
+    }
 
     getAccount() {
       return this._fetchSharedCacheURL({
@@ -622,7 +634,7 @@
           }
         },
       });
-    },
+    }
 
     getAvatarChangeUrl() {
       return this._fetchSharedCacheURL({
@@ -634,14 +646,14 @@
           }
         },
       });
-    },
+    }
 
     getExternalIds() {
       return this._restApiHelper.fetchJSON({
         url: '/accounts/self/external.ids',
         reportUrlAsIs: true,
       });
-    },
+    }
 
     deleteAccountIdentity(id) {
       return this._restApiHelper.send({
@@ -651,7 +663,7 @@
         parseResponse: true,
         reportUrlAsIs: true,
       });
-    },
+    }
 
     /**
      * @param {string} userId the ID of the user usch as an email address.
@@ -662,14 +674,14 @@
         url: `/accounts/${encodeURIComponent(userId)}/detail`,
         anonymizedUrl: '/accounts/*/detail',
       });
-    },
+    }
 
     getAccountEmails() {
       return this._fetchSharedCacheURL({
         url: '/accounts/self/emails',
         reportUrlAsIs: true,
       });
-    },
+    }
 
     /**
      * @param {string} email
@@ -682,7 +694,7 @@
         errFn: opt_errFn,
         anonymizedUrl: '/account/self/emails/*',
       });
-    },
+    }
 
     /**
      * @param {string} email
@@ -695,7 +707,7 @@
         errFn: opt_errFn,
         anonymizedUrl: '/accounts/self/email/*',
       });
-    },
+    }
 
     /**
      * @param {string} email
@@ -724,7 +736,7 @@
           this._cache.set('/accounts/self/emails', emails);
         }
       });
-    },
+    }
 
     /**
      * @param {?Object} obj
@@ -738,7 +750,7 @@
         this._cache.set('/accounts/self/detail',
             Object.assign({}, cachedAccount, obj));
       }
-    },
+    }
 
     /**
      * @param {string} name
@@ -755,7 +767,7 @@
       };
       return this._restApiHelper.send(req)
           .then(newName => this._updateCachedAccount({name: newName}));
-    },
+    }
 
     /**
      * @param {string} username
@@ -772,7 +784,7 @@
       };
       return this._restApiHelper.send(req)
           .then(newName => this._updateCachedAccount({username: newName}));
-    },
+    }
 
     /**
      * @param {string} status
@@ -789,28 +801,28 @@
       };
       return this._restApiHelper.send(req)
           .then(newStatus => this._updateCachedAccount({status: newStatus}));
-    },
+    }
 
     getAccountStatus(userId) {
       return this._restApiHelper.fetchJSON({
         url: `/accounts/${encodeURIComponent(userId)}/status`,
         anonymizedUrl: '/accounts/*/status',
       });
-    },
+    }
 
     getAccountGroups() {
       return this._restApiHelper.fetchJSON({
         url: '/accounts/self/groups',
         reportUrlAsIs: true,
       });
-    },
+    }
 
     getAccountAgreements() {
       return this._restApiHelper.fetchJSON({
         url: '/accounts/self/agreements',
         reportUrlAsIs: true,
       });
-    },
+    }
 
     saveAccountAgreement(name) {
       return this._restApiHelper.send({
@@ -819,7 +831,7 @@
         body: name,
         reportUrlAsIs: true,
       });
-    },
+    }
 
     /**
      * @param {string=} opt_params
@@ -835,7 +847,7 @@
         url: '/accounts/self/capabilities' + queryString,
         anonymizedUrl: '/accounts/self/capabilities?q=*',
       });
-    },
+    }
 
     getLoggedIn() {
       return this.getAccount().then(account => {
@@ -843,7 +855,7 @@
       }).catch(() => {
         return false;
       });
-    },
+    }
 
     getIsAdmin() {
       return this.getLoggedIn().then(isLoggedIn => {
@@ -855,18 +867,18 @@
       }).then(capabilities => {
         return capabilities && capabilities.administrateServer;
       });
-    },
+    }
 
     checkCredentials() {
       return this._restApiHelper.checkCredentials();
-    },
+    }
 
     getDefaultPreferences() {
       return this._fetchSharedCacheURL({
         url: '/config/server/preferences',
         reportUrlAsIs: true,
       });
-    },
+    }
 
     getPreferences() {
       return this.getLoggedIn().then(loggedIn => {
@@ -892,14 +904,14 @@
           size_bar_in_change_table: true,
         });
       });
-    },
+    }
 
     getWatchedProjects() {
       return this._fetchSharedCacheURL({
         url: '/accounts/self/watched.projects',
         reportUrlAsIs: true,
       });
-    },
+    }
 
     /**
      * @param {string} projects
@@ -914,7 +926,7 @@
         parseResponse: true,
         reportUrlAsIs: true,
       });
-    },
+    }
 
     /**
      * @param {string} projects
@@ -928,11 +940,11 @@
         errFn: opt_errFn,
         reportUrlAsIs: true,
       });
-    },
+    }
 
     _isNarrowScreen() {
       return window.innerWidth < MAX_UNIFIED_DEFAULT_WINDOW_WIDTH_PX;
-    },
+    }
 
     /**
      * @param {number=} opt_changesPerPage
@@ -988,7 +1000,7 @@
         }
         return response;
       });
-    },
+    }
 
     /**
      * Inserts a change into _projectLookup iff it has a valid structure.
@@ -998,7 +1010,7 @@
       if (change && change.project && change._number) {
         this.setInProjectLookup(change._number, change.project);
       }
-    },
+    }
 
     /**
      * TODO (beckysiegel) this needs to be rewritten with the optional param
@@ -1012,7 +1024,7 @@
     getChangeActionURL(changeNum, opt_patchNum, endpoint) {
       return this._changeBaseURL(changeNum, opt_patchNum)
           .then(url => url + endpoint);
-    },
+    }
 
     /**
      * @param {number|string} changeNum
@@ -1043,7 +1055,7 @@
             changeNum, optionsHex, opt_errFn, opt_cancelCondition)
             .then(GrReviewerUpdatesParser.parse);
       });
-    },
+    }
 
     /**
      * @param {number|string} changeNum
@@ -1059,7 +1071,7 @@
       );
       return this._getChangeDetail(changeNum, optionsHex, opt_errFn,
           opt_cancelCondition);
-    },
+    }
 
     /**
      * @param {number|string} changeNum
@@ -1109,7 +1121,7 @@
           });
         });
       });
-    },
+    }
 
     /**
      * @param {number|string} changeNum
@@ -1122,7 +1134,7 @@
         patchNum,
         reportEndpointAsIs: true,
       });
-    },
+    }
 
     /**
      * @param {number|string} changeNum
@@ -1143,7 +1155,7 @@
         params,
         reportEndpointAsIs: true,
       });
-    },
+    }
 
     /**
      * @param {number|string} changeNum
@@ -1161,7 +1173,7 @@
         endpoint,
         anonymizedEndpoint,
       });
-    },
+    }
 
     /**
      * @param {number|string} changeNum
@@ -1176,7 +1188,7 @@
         patchNum,
         anonymizedEndpoint: '/files?q=*',
       });
-    },
+    }
 
     /**
      * @param {number|string} changeNum
@@ -1189,7 +1201,7 @@
           res.files);
       }
       return this.getChangeFiles(changeNum, patchRange);
-    },
+    }
 
     /**
      * The closure compiler doesn't realize this.specialFilePathCompare is
@@ -1201,7 +1213,7 @@
         if (!files) return;
         return Object.keys(files).sort(this.specialFilePathCompare);
       });
-    },
+    }
 
     getChangeRevisionActions(changeNum, patchNum) {
       const req = {
@@ -1219,7 +1231,7 @@
         }
         return revisionActions;
       });
-    },
+    }
 
     /**
      * @param {number|string} changeNum
@@ -1229,7 +1241,7 @@
     getChangeSuggestedReviewers(changeNum, inputVal, opt_errFn) {
       return this._getChangeSuggestedGroup('REVIEWER', changeNum, inputVal,
           opt_errFn);
-    },
+    }
 
     /**
      * @param {number|string} changeNum
@@ -1239,7 +1251,7 @@
     getChangeSuggestedCCs(changeNum, inputVal, opt_errFn) {
       return this._getChangeSuggestedGroup('CC', changeNum, inputVal,
           opt_errFn);
-    },
+    }
 
     _getChangeSuggestedGroup(reviewerState, changeNum, inputVal, opt_errFn) {
       // More suggestions may obscure content underneath in the reply dialog,
@@ -1253,7 +1265,7 @@
         params,
         reportEndpointAsIs: true,
       });
-    },
+    }
 
     /**
      * @param {number|string} changeNum
@@ -1264,7 +1276,7 @@
         endpoint: '/in',
         reportEndpointAsIs: true,
       });
-    },
+    }
 
     _computeFilter(filter) {
       if (filter && filter.startsWith('^')) {
@@ -1275,7 +1287,7 @@
         filter = '';
       }
       return filter;
-    },
+    }
 
     /**
      * @param {string} filter
@@ -1287,7 +1299,7 @@
 
       return `/groups/?n=${groupsPerPage + 1}&S=${offset}` +
         this._computeFilter(filter);
-    },
+    }
 
     /**
      * @param {string} filter
@@ -1323,15 +1335,15 @@
 
       return `/projects/?n=${reposPerPage + 1}&S=${offset}` +
         `&query=${encodedFilter}`;
-    },
+    }
 
     invalidateGroupsCache() {
       this._restApiHelper.invalidateFetchPromisesPrefix('/groups/?');
-    },
+    }
 
     invalidateReposCache() {
       this._restApiHelper.invalidateFetchPromisesPrefix('/projects/?');
-    },
+    }
 
     /**
      * @param {string} filter
@@ -1346,7 +1358,7 @@
         url,
         anonymizedUrl: '/groups/?*',
       });
-    },
+    }
 
     /**
      * @param {string} filter
@@ -1363,7 +1375,7 @@
         url,
         anonymizedUrl: '/projects/?*',
       });
-    },
+    }
 
     setRepoHead(repo, ref) {
       // TODO(kaspern): Rename rest api from /projects/ to /repos/ once backend
@@ -1374,7 +1386,7 @@
         body: {ref},
         anonymizedUrl: '/projects/*/HEAD',
       });
-    },
+    }
 
     /**
      * @param {string} filter
@@ -1397,7 +1409,7 @@
         errFn: opt_errFn,
         anonymizedUrl: '/projects/*/branches?*',
       });
-    },
+    }
 
     /**
      * @param {string} filter
@@ -1421,7 +1433,7 @@
         errFn: opt_errFn,
         anonymizedUrl: '/projects/*/tags',
       });
-    },
+    }
 
     /**
      * @param {string} filter
@@ -1440,7 +1452,7 @@
         errFn: opt_errFn,
         anonymizedUrl: '/plugins/?all',
       });
-    },
+    }
 
     getRepoAccessRights(repoName, opt_errFn) {
       // TODO(kaspern): Rename rest api from /projects/ to /repos/ once backend
@@ -1450,7 +1462,7 @@
         errFn: opt_errFn,
         anonymizedUrl: '/projects/*/access',
       });
-    },
+    }
 
     setRepoAccessRights(repoName, repoInfo) {
       // TODO(kaspern): Rename rest api from /projects/ to /repos/ once backend
@@ -1461,7 +1473,7 @@
         body: repoInfo,
         anonymizedUrl: '/projects/*/access',
       });
-    },
+    }
 
     setRepoAccessRightsForReview(projectName, projectInfo) {
       return this._restApiHelper.send({
@@ -1471,7 +1483,7 @@
         parseResponse: true,
         anonymizedUrl: '/projects/*/access:review',
       });
-    },
+    }
 
     /**
      * @param {string} inputVal
@@ -1487,7 +1499,7 @@
         params,
         reportUrlAsIs: true,
       });
-    },
+    }
 
     /**
      * @param {string} inputVal
@@ -1507,7 +1519,7 @@
         params,
         reportUrlAsIs: true,
       });
-    },
+    }
 
     /**
      * @param {string} inputVal
@@ -1526,15 +1538,15 @@
         params,
         anonymizedUrl: '/accounts/?n=*',
       });
-    },
+    }
 
     addChangeReviewer(changeNum, reviewerID) {
       return this._sendChangeReviewerRequest('POST', changeNum, reviewerID);
-    },
+    }
 
     removeChangeReviewer(changeNum, reviewerID) {
       return this._sendChangeReviewerRequest('DELETE', changeNum, reviewerID);
-    },
+    }
 
     _sendChangeReviewerRequest(method, changeNum, reviewerID) {
       return this.getChangeActionURL(changeNum, null, '/reviewers')
@@ -1553,7 +1565,7 @@
 
             return this._restApiHelper.send({method, url, body});
           });
-    },
+    }
 
     getRelatedChanges(changeNum, patchNum) {
       return this._getChangeURLAndFetch({
@@ -1562,7 +1574,7 @@
         patchNum,
         reportEndpointAsIs: true,
       });
-    },
+    }
 
     getChangesSubmittedTogether(changeNum) {
       return this._getChangeURLAndFetch({
@@ -1570,7 +1582,7 @@
         endpoint: '/submitted_together?o=NON_VISIBLE_CHANGES',
         reportEndpointAsIs: true,
       });
-    },
+    }
 
     getChangeConflicts(changeNum) {
       const options = this.listChangesOptionsToHex(
@@ -1579,14 +1591,14 @@
       );
       const params = {
         O: options,
-        q: 'status:open is:mergeable conflicts:' + changeNum,
+        q: 'status:open conflicts:' + changeNum,
       };
       return this._restApiHelper.fetchJSON({
         url: '/changes/',
         params,
         anonymizedUrl: '/changes/conflicts:*',
       });
-    },
+    }
 
     getChangeCherryPicks(project, changeID, changeNum) {
       const options = this.listChangesOptionsToHex(
@@ -1608,7 +1620,7 @@
         params,
         anonymizedUrl: '/changes/change:*',
       });
-    },
+    }
 
     getChangesWithSameTopic(topic, changeNum) {
       const options = this.listChangesOptionsToHex(
@@ -1631,7 +1643,7 @@
         params,
         anonymizedUrl: '/changes/topic:*',
       });
-    },
+    }
 
     getReviewedFiles(changeNum, patchNum) {
       return this._getChangeURLAndFetch({
@@ -1640,7 +1652,7 @@
         patchNum,
         reportEndpointAsIs: true,
       });
-    },
+    }
 
     /**
      * @param {number|string} changeNum
@@ -1658,7 +1670,7 @@
         errFn: opt_errFn,
         anonymizedEndpoint: '/files/*/reviewed',
       });
-    },
+    }
 
     /**
      * @param {number|string} changeNum
@@ -1679,7 +1691,7 @@
           errFn: opt_errFn,
         });
       });
-    },
+    }
 
     getChangeEdit(changeNum, opt_download_commands) {
       const params = opt_download_commands ? {'download-commands': true} : null;
@@ -1692,7 +1704,7 @@
           reportEndpointAsIs: true,
         });
       });
-    },
+    }
 
     /**
      * @param {string} project
@@ -1722,7 +1734,7 @@
         parseResponse: true,
         reportUrlAsIs: true,
       });
-    },
+    }
 
     /**
      * @param {number|string} changeNum
@@ -1750,7 +1762,7 @@
           return {content, type, ok: true};
         });
       });
-    },
+    }
 
     /**
      * Gets a file in a specific change and revision.
@@ -1769,7 +1781,7 @@
         headers: {Accept: 'application/json'},
         anonymizedEndpoint: '/files/*/content',
       });
-    },
+    }
 
     /**
      * Gets a file in a change edit.
@@ -1784,7 +1796,7 @@
         headers: {Accept: 'application/json'},
         anonymizedEndpoint: '/edit/*',
       });
-    },
+    }
 
     rebaseChangeEdit(changeNum) {
       return this._getChangeURLAndSend({
@@ -1793,7 +1805,7 @@
         endpoint: '/edit:rebase',
         reportEndpointAsIs: true,
       });
-    },
+    }
 
     deleteChangeEdit(changeNum) {
       return this._getChangeURLAndSend({
@@ -1802,7 +1814,7 @@
         endpoint: '/edit',
         reportEndpointAsIs: true,
       });
-    },
+    }
 
     restoreFileInChangeEdit(changeNum, restore_path) {
       return this._getChangeURLAndSend({
@@ -1812,7 +1824,7 @@
         body: {restore_path},
         reportEndpointAsIs: true,
       });
-    },
+    }
 
     renameFileInChangeEdit(changeNum, old_path, new_path) {
       return this._getChangeURLAndSend({
@@ -1822,7 +1834,7 @@
         body: {old_path, new_path},
         reportEndpointAsIs: true,
       });
-    },
+    }
 
     deleteFileInChangeEdit(changeNum, path) {
       return this._getChangeURLAndSend({
@@ -1831,7 +1843,7 @@
         endpoint: '/edit/' + encodeURIComponent(path),
         anonymizedEndpoint: '/edit/*',
       });
-    },
+    }
 
     saveChangeEdit(changeNum, path, contents) {
       return this._getChangeURLAndSend({
@@ -1842,7 +1854,7 @@
         contentType: 'text/plain',
         anonymizedEndpoint: '/edit/*',
       });
-    },
+    }
 
     // Deprecated, prefer to use putChangeCommitMessage instead.
     saveChangeCommitMessageEdit(changeNum, message) {
@@ -1853,7 +1865,7 @@
         body: {message},
         reportEndpointAsIs: true,
       });
-    },
+    }
 
     publishChangeEdit(changeNum) {
       return this._getChangeURLAndSend({
@@ -1862,7 +1874,7 @@
         endpoint: '/edit:publish',
         reportEndpointAsIs: true,
       });
-    },
+    }
 
     putChangeCommitMessage(changeNum, message) {
       return this._getChangeURLAndSend({
@@ -1872,7 +1884,7 @@
         body: {message},
         reportEndpointAsIs: true,
       });
-    },
+    }
 
     saveChangeStarred(changeNum, starred) {
       // Some servers may require the project name to be provided
@@ -1887,7 +1899,7 @@
           anonymizedUrl: '/accounts/self/starred.changes/*',
         });
       });
-    },
+    }
 
     saveChangeReviewed(changeNum, reviewed) {
       return this._getChangeURLAndSend({
@@ -1895,7 +1907,7 @@
         method: 'PUT',
         endpoint: reviewed ? '/reviewed' : '/unreviewed',
       });
-    },
+    }
 
     /**
      * Public version of the _restApiHelper.send method preserved for plugins.
@@ -1919,7 +1931,7 @@
         contentType: opt_contentType,
         headers: opt_headers,
       });
-    },
+    }
 
     /**
      * @param {number|string} changeNum
@@ -1961,7 +1973,7 @@
       }
 
       return this._getChangeURLAndFetch(req);
-    },
+    }
 
     /**
      * @param {number|string} changeNum
@@ -1973,7 +1985,7 @@
     getDiffComments(changeNum, opt_basePatchNum, opt_patchNum, opt_path) {
       return this._getDiffComments(changeNum, '/comments', opt_basePatchNum,
           opt_patchNum, opt_path);
-    },
+    }
 
     /**
      * @param {number|string} changeNum
@@ -1985,7 +1997,7 @@
     getDiffRobotComments(changeNum, opt_basePatchNum, opt_patchNum, opt_path) {
       return this._getDiffComments(changeNum, '/robotcomments',
           opt_basePatchNum, opt_patchNum, opt_path);
-    },
+    }
 
     /**
      * If the user is logged in, fetch the user's draft diff comments. If there
@@ -2004,7 +2016,7 @@
         return this._getDiffComments(changeNum, '/drafts', opt_basePatchNum,
             opt_patchNum, opt_path);
       });
-    },
+    }
 
     _setRange(comments, comment) {
       if (comment.in_reply_to && !comment.range) {
@@ -2016,7 +2028,7 @@
         }
       }
       return comment;
-    },
+    }
 
     _setRanges(comments) {
       comments = comments || [];
@@ -2027,7 +2039,7 @@
         this._setRange(comments, comment);
       }
       return comments;
-    },
+    }
 
     /**
      * @param {number|string} changeNum
@@ -2100,7 +2112,7 @@
           comments,
         });
       });
-    },
+    }
 
     /**
      * @param {number|string} changeNum
@@ -2110,15 +2122,15 @@
     _getDiffCommentsFetchURL(changeNum, endpoint, opt_patchNum) {
       return this._changeBaseURL(changeNum, opt_patchNum)
           .then(url => url + endpoint);
-    },
+    }
 
     saveDiffDraft(changeNum, patchNum, draft) {
       return this._sendDiffDraftRequest('PUT', changeNum, patchNum, draft);
-    },
+    }
 
     deleteDiffDraft(changeNum, patchNum, draft) {
       return this._sendDiffDraftRequest('DELETE', changeNum, patchNum, draft);
-    },
+    }
 
     /**
      * @returns {boolean} Whether there are pending diff draft sends.
@@ -2126,7 +2138,7 @@
     hasPendingDiffDrafts() {
       const promises = this._pendingRequests[Requests.SEND_DIFF_DRAFT];
       return promises && promises.length;
-    },
+    }
 
     /**
      * @returns {!Promise<undefined>} A promise that resolves when all pending
@@ -2137,7 +2149,7 @@
           .then(() => {
             this._pendingRequests[Requests.SEND_DIFF_DRAFT] = [];
           });
-    },
+    }
 
     _sendDiffDraftRequest(method, changeNum, patchNum, draft) {
       const isCreate = !draft.id && method === 'PUT';
@@ -2173,7 +2185,7 @@
       }
 
       return promise;
-    },
+    }
 
     getCommitInfo(project, commit) {
       return this._restApiHelper.fetchJSON({
@@ -2181,7 +2193,7 @@
             '/commits/' + encodeURIComponent(commit),
         anonymizedUrl: '/projects/*/comments/*',
       });
-    },
+    }
 
     _fetchB64File(url) {
       return this._restApiHelper.fetch({url: this.getBaseUrl() + url})
@@ -2195,7 +2207,7 @@
                   return {body: text, type};
                 });
           });
-    },
+    }
 
     /**
      * @param {string} changeId
@@ -2210,7 +2222,7 @@
         url = `${url}/files/${encodeURIComponent(path)}/content${parent}`;
         return this._fetchB64File(url);
       });
-    },
+    }
 
     getImagesForDiff(changeNum, diff, patchRange) {
       let promiseA;
@@ -2252,7 +2264,7 @@
 
         return {baseImage, revisionImage};
       });
-    },
+    }
 
     /**
      * @param {number|string} changeNum
@@ -2273,7 +2285,7 @@
         }
         return url;
       });
-    },
+    }
 
     /**
      * @suppress {checkTypes}
@@ -2289,7 +2301,7 @@
         parseResponse: true,
         reportUrlAsIs: true,
       });
-    },
+    }
 
     /**
      * @suppress {checkTypes}
@@ -2305,7 +2317,7 @@
         parseResponse: true,
         reportUrlAsIs: true,
       });
-    },
+    }
 
     deleteAccountHttpPassword() {
       return this._restApiHelper.send({
@@ -2313,7 +2325,7 @@
         url: '/accounts/self/password.http',
         reportUrlAsIs: true,
       });
-    },
+    }
 
     /**
      * @suppress {checkTypes}
@@ -2328,14 +2340,14 @@
         parseResponse: true,
         reportUrlAsIs: true,
       });
-    },
+    }
 
     getAccountSSHKeys() {
       return this._fetchSharedCacheURL({
         url: '/accounts/self/sshkeys',
         reportUrlAsIs: true,
       });
-    },
+    }
 
     addAccountSSHKey(key) {
       const req = {
@@ -2356,7 +2368,7 @@
             if (!obj.valid) { return Promise.reject(new Error('error')); }
             return obj;
           });
-    },
+    }
 
     deleteAccountSSHKey(id) {
       return this._restApiHelper.send({
@@ -2364,14 +2376,14 @@
         url: '/accounts/self/sshkeys/' + id,
         anonymizedUrl: '/accounts/self/sshkeys/*',
       });
-    },
+    }
 
     getAccountGPGKeys() {
       return this._restApiHelper.fetchJSON({
         url: '/accounts/self/gpgkeys',
         reportUrlAsIs: true,
       });
-    },
+    }
 
     addAccountGPGKey(key) {
       const req = {
@@ -2391,7 +2403,7 @@
             if (!obj) { return Promise.reject(new Error('error')); }
             return obj;
           });
-    },
+    }
 
     deleteAccountGPGKey(id) {
       return this._restApiHelper.send({
@@ -2399,7 +2411,7 @@
         url: '/accounts/self/gpgkeys/' + id,
         anonymizedUrl: '/accounts/self/gpgkeys/*',
       });
-    },
+    }
 
     deleteVote(changeNum, account, label) {
       return this._getChangeURLAndSend({
@@ -2408,7 +2420,7 @@
         endpoint: `/reviewers/${account}/votes/${encodeURIComponent(label)}`,
         anonymizedEndpoint: '/reviewers/*/votes/*',
       });
-    },
+    }
 
     setDescription(changeNum, patchNum, desc) {
       return this._getChangeURLAndSend({
@@ -2418,7 +2430,7 @@
         body: {description: desc},
         reportUrlAsIs: true,
       });
-    },
+    }
 
     confirmEmail(token) {
       const req = {
@@ -2433,7 +2445,7 @@
         }
         return null;
       });
-    },
+    }
 
     getCapabilities(opt_errFn) {
       return this._restApiHelper.fetchJSON({
@@ -2441,7 +2453,7 @@
         errFn: opt_errFn,
         reportUrlAsIs: true,
       });
-    },
+    }
 
     getTopMenus(opt_errFn) {
       return this._fetchSharedCacheURL({
@@ -2449,7 +2461,7 @@
         errFn: opt_errFn,
         reportUrlAsIs: true,
       });
-    },
+    }
 
     setAssignee(changeNum, assignee) {
       return this._getChangeURLAndSend({
@@ -2459,7 +2471,7 @@
         body: {assignee},
         reportUrlAsIs: true,
       });
-    },
+    }
 
     deleteAssignee(changeNum) {
       return this._getChangeURLAndSend({
@@ -2468,14 +2480,14 @@
         endpoint: '/assignee',
         reportUrlAsIs: true,
       });
-    },
+    }
 
     probePath(path) {
       return fetch(new Request(path, {method: 'HEAD'}))
           .then(response => {
             return response.ok;
           });
-    },
+    }
 
     /**
      * @param {number|string} changeNum
@@ -2498,7 +2510,7 @@
           return 'Change marked as Work In Progress.';
         }
       });
-    },
+    }
 
     /**
      * @param {number|string} changeNum
@@ -2514,7 +2526,7 @@
         errFn: opt_errFn,
         reportUrlAsIs: true,
       });
-    },
+    }
 
     /**
      * @suppress {checkTypes}
@@ -2531,7 +2543,7 @@
         parseResponse: true,
         anonymizedEndpoint: '/comments/*/delete',
       });
-    },
+    }
 
     /**
      * Given a changeNum, gets the change.
@@ -2550,7 +2562,7 @@
         if (!res || !res.length) { return null; }
         return res[0];
       });
-    },
+    }
 
     /**
      * @param {string|number} changeNum
@@ -2563,7 +2575,7 @@
             'One of them must be invalid.');
       }
       this._projectLookup[changeNum] = project;
-    },
+    }
 
     /**
      * Checks in _projectLookup for the changeNum. If it exists, returns the
@@ -2587,7 +2599,7 @@
         this.setInProjectLookup(changeNum, change.project);
         return change.project;
       });
-    },
+    }
 
     /**
      * Alias for _changeBaseURL.then(send).
@@ -2614,7 +2626,7 @@
             (anonymizedBaseUrl + anonymizedEndpoint) : undefined,
         });
       });
-    },
+    }
 
     /**
      * Alias for _changeBaseURL.then(_fetchJSON).
@@ -2636,7 +2648,7 @@
             (anonymizedBaseUrl + anonymizedEndpoint) : undefined,
         });
       });
-    },
+    }
 
     /**
      * Execute a change action or revision action on a change.
@@ -2658,7 +2670,7 @@
         body: opt_payload,
         errFn: opt_errFn,
       });
-    },
+    }
 
     /**
      * Get blame information for the given diff.
@@ -2678,7 +2690,7 @@
         params: opt_base ? {base: 't'} : undefined,
         anonymizedEndpoint: '/files/*/blame',
       });
-    },
+    }
 
     /**
      * Modify the given create draft request promise so that it fails and throws
@@ -2707,7 +2719,7 @@
         }
         return result;
       });
-    },
+    }
 
     /**
      * Fetch a project dashboard definition.
@@ -2726,7 +2738,7 @@
         errFn: opt_errFn,
         anonymizedUrl: '/projects/*/dashboards/*',
       });
-    },
+    }
 
     /**
      * @param {string} filter
@@ -2742,7 +2754,7 @@
         url: `/Documentation/?q=${encodedFilter}`,
         anonymizedUrl: '/Documentation/?*',
       });
-    },
+    }
 
     getMergeable(changeNum) {
       return this._getChangeURLAndFetch({
@@ -2751,7 +2763,7 @@
         parseResponse: true,
         reportEndpointAsIs: true,
       });
-    },
+    }
 
     deleteDraftComments(query) {
       return this._restApiHelper.send({
@@ -2759,6 +2771,8 @@
         url: '/accounts/self/drafts:delete',
         body: {query},
       });
-    },
-  });
+    }
+  }
+
+  customElements.define(GrRestApiInterface.is, GrRestApiInterface);
 })();
diff --git a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface_test.html b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface_test.html
index 9f01f48..a598d0d 100644
--- a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-rest-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>
diff --git a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-apis/gr-rest-api-helper_test.html b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-apis/gr-rest-api-helper_test.html
index 4eaf1bc..446cfc8 100644
--- a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-apis/gr-rest-api-helper_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-apis/gr-rest-api-helper_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-rest-api-helper</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>
diff --git a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-reviewer-updates-parser_test.html b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-reviewer-updates-parser_test.html
index fdf79af..ad3e4ca 100644
--- a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-reviewer-updates-parser_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-reviewer-updates-parser_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-reviewer-updates-parser</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>
diff --git a/polygerrit-ui/app/elements/shared/gr-select/gr-select.js b/polygerrit-ui/app/elements/shared/gr-select/gr-select.js
index 05267ba..152834f 100644
--- a/polygerrit-ui/app/elements/shared/gr-select/gr-select.js
+++ b/polygerrit-ui/app/elements/shared/gr-select/gr-select.js
@@ -17,29 +17,29 @@
 (function() {
   'use strict';
 
-  Polymer({
-    is: 'gr-select',
+  /**
+    * @appliesMixin Gerrit.FireMixin
+    */
+  class GrSelect extends Polymer.mixinBehaviors( [
+    Gerrit.FireBehavior,
+  ], Polymer.GestureEventListeners(
+      Polymer.LegacyElementMixin(
+          Polymer.Element))) {
+    static get is() { return 'gr-select'; }
 
-    properties: {
-      bindValue: {
-        type: String,
-        notify: true,
-        observer: '_updateValue',
-      },
-    },
-
-    behaviors: [
-      Gerrit.FireBehavior,
-    ],
-
-    listeners: {
-      'change': '_valueChanged',
-      'dom-change': '_updateValue',
-    },
+    static get properties() {
+      return {
+        bindValue: {
+          type: String,
+          notify: true,
+          observer: '_updateValue',
+        },
+      };
+    }
 
     get nativeSelect() {
       return this.$$('select');
-    },
+    }
 
     _updateValue() {
       // It's possible to have a value of 0.
@@ -53,21 +53,32 @@
           this.nativeSelect.value = this.bindValue;
         }, 1);
       }
-    },
+    }
 
     _valueChanged() {
       this.bindValue = this.nativeSelect.value;
-    },
+    }
 
     focus() {
       this.nativeSelect.focus();
-    },
+    }
+
+    created() {
+      super.created();
+      this.addEventListener('change',
+          () => this._valueChanged());
+      this.addEventListener('dom-change',
+          () => this._updateValue());
+    }
 
     ready() {
+      super.ready();
       // If not set via the property, set bind-value to the element value.
       if (this.bindValue == undefined && this.nativeSelect.options.length > 0) {
         this.bindValue = this.nativeSelect.value;
       }
-    },
-  });
+    }
+  }
+
+  customElements.define(GrSelect.is, GrSelect);
 })();
diff --git a/polygerrit-ui/app/elements/shared/gr-select/gr-select_test.html b/polygerrit-ui/app/elements/shared/gr-select/gr-select_test.html
index b3abe5f..cae61056 100644
--- a/polygerrit-ui/app/elements/shared/gr-select/gr-select_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-select/gr-select_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-select</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>
diff --git a/polygerrit-ui/app/elements/shared/gr-shell-command/gr-shell-command.js b/polygerrit-ui/app/elements/shared/gr-shell-command/gr-shell-command.js
index 2c546cc..9456991 100644
--- a/polygerrit-ui/app/elements/shared/gr-shell-command/gr-shell-command.js
+++ b/polygerrit-ui/app/elements/shared/gr-shell-command/gr-shell-command.js
@@ -17,16 +17,22 @@
 (function() {
   'use strict';
 
-  Polymer({
-    is: 'gr-shell-command',
+  class GrShellCommand extends Polymer.GestureEventListeners(
+      Polymer.LegacyElementMixin(
+          Polymer.Element)) {
+    static get is() { return 'gr-shell-command'; }
 
-    properties: {
-      command: String,
-      label: String,
-    },
+    static get properties() {
+      return {
+        command: String,
+        label: String,
+      };
+    }
 
     focusOnCopy() {
       this.$$('gr-copy-clipboard').focusOnCopy();
-    },
-  });
+    }
+  }
+
+  customElements.define(GrShellCommand.is, GrShellCommand);
 })();
diff --git a/polygerrit-ui/app/elements/shared/gr-shell-command/gr-shell-command_test.html b/polygerrit-ui/app/elements/shared/gr-shell-command/gr-shell-command_test.html
index 3f2f8ba..a4bdf58 100644
--- a/polygerrit-ui/app/elements/shared/gr-shell-command/gr-shell-command_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-shell-command/gr-shell-command_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-shell-command</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>
diff --git a/polygerrit-ui/app/elements/shared/gr-storage/gr-storage.js b/polygerrit-ui/app/elements/shared/gr-storage/gr-storage.js
index f6ade6e..7950e51 100644
--- a/polygerrit-ui/app/elements/shared/gr-storage/gr-storage.js
+++ b/polygerrit-ui/app/elements/shared/gr-storage/gr-storage.js
@@ -28,52 +28,56 @@
     'editablecontent:',
   ];
 
-  Polymer({
-    is: 'gr-storage',
+  class GrStorage extends Polymer.GestureEventListeners(
+      Polymer.LegacyElementMixin(
+          Polymer.Element)) {
+    static get is() { return 'gr-storage'; }
 
-    properties: {
-      _lastCleanup: Number,
-      /** @type {?Storage} */
-      _storage: {
-        type: Object,
-        value() {
-          return window.localStorage;
+    static get properties() {
+      return {
+        _lastCleanup: Number,
+        /** @type {?Storage} */
+        _storage: {
+          type: Object,
+          value() {
+            return window.localStorage;
+          },
         },
-      },
-      _exceededQuota: {
-        type: Boolean,
-        value: false,
-      },
-    },
+        _exceededQuota: {
+          type: Boolean,
+          value: false,
+        },
+      };
+    }
 
     getDraftComment(location) {
       this._cleanupItems();
       return this._getObject(this._getDraftKey(location));
-    },
+    }
 
     setDraftComment(location, message) {
       const key = this._getDraftKey(location);
       this._setObject(key, {message, updated: Date.now()});
-    },
+    }
 
     eraseDraftComment(location) {
       const key = this._getDraftKey(location);
       this._storage.removeItem(key);
-    },
+    }
 
     getEditableContentItem(key) {
       this._cleanupItems();
       return this._getObject(this._getEditableContentKey(key));
-    },
+    }
 
     setEditableContentItem(key, message) {
       this._setObject(this._getEditableContentKey(key),
           {message, updated: Date.now()});
-    },
+    }
 
     eraseEditableContentItem(key) {
       this._storage.removeItem(this._getEditableContentKey(key));
-    },
+    }
 
     _getDraftKey(location) {
       const range = location.range ?
@@ -86,11 +90,11 @@
         key = key + ':' + range;
       }
       return key;
-    },
+    }
 
     _getEditableContentKey(key) {
       return `editablecontent:${key}`;
-    },
+    }
 
     _cleanupItems() {
       // Throttle cleanup to the throttle interval.
@@ -113,13 +117,13 @@
           }
         }
       }
-    },
+    }
 
     _getObject(key) {
       const serial = this._storage.getItem(key);
       if (!serial) { return null; }
       return JSON.parse(serial);
-    },
+    }
 
     _setObject(key, obj) {
       if (this._exceededQuota) { return; }
@@ -136,6 +140,8 @@
           throw exc;
         }
       }
-    },
-  });
+    }
+  }
+
+  customElements.define(GrStorage.is, GrStorage);
 })();
diff --git a/polygerrit-ui/app/elements/shared/gr-storage/gr-storage_test.html b/polygerrit-ui/app/elements/shared/gr-storage/gr-storage_test.html
index 0482584..5cd4d3e 100644
--- a/polygerrit-ui/app/elements/shared/gr-storage/gr-storage_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-storage/gr-storage_test.html
@@ -17,7 +17,7 @@
 -->
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-storage</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>
diff --git a/polygerrit-ui/app/elements/shared/gr-textarea/gr-textarea.js b/polygerrit-ui/app/elements/shared/gr-textarea/gr-textarea.js
index 4c4b038..5011067 100644
--- a/polygerrit-ui/app/elements/shared/gr-textarea/gr-textarea.js
+++ b/polygerrit-ui/app/elements/shared/gr-textarea/gr-textarea.js
@@ -52,73 +52,81 @@
     {value: '😜', match: 'winking tongue ;)'},
   ];
 
-  Polymer({
-    is: 'gr-textarea',
-
+  /**
+    * @appliesMixin Gerrit.FireMixin
+    * @appliesMixin Gerrit.KeyboardShortcutMixin
+    */
+  class GrTextarea extends Polymer.mixinBehaviors( [
+    Gerrit.FireBehavior,
+    Gerrit.KeyboardShortcutBehavior,
+  ], Polymer.GestureEventListeners(
+      Polymer.LegacyElementMixin(
+          Polymer.Element))) {
+    static get is() { return 'gr-textarea'; }
     /**
      * @event bind-value-changed
      */
 
-    properties: {
-      autocomplete: Boolean,
-      disabled: Boolean,
-      rows: Number,
-      maxRows: Number,
-      placeholder: String,
-      text: {
-        type: String,
-        notify: true,
-        observer: '_handleTextChanged',
-      },
-      hideBorder: {
-        type: Boolean,
-        value: false,
-      },
-      /** Text input should be rendered in monspace font.  */
-      monospace: {
-        type: Boolean,
-        value: false,
-      },
-      /** Text input should be rendered in code font, which is smaller than the
+    static get properties() {
+      return {
+        autocomplete: Boolean,
+        disabled: Boolean,
+        rows: Number,
+        maxRows: Number,
+        placeholder: String,
+        text: {
+          type: String,
+          notify: true,
+          observer: '_handleTextChanged',
+        },
+        hideBorder: {
+          type: Boolean,
+          value: false,
+        },
+        /** Text input should be rendered in monspace font.  */
+        monospace: {
+          type: Boolean,
+          value: false,
+        },
+        /** Text input should be rendered in code font, which is smaller than the
           standard monospace font. */
-      code: {
-        type: Boolean,
-        value: false,
-      },
-      /** @type(?number) */
-      _colonIndex: Number,
-      _currentSearchString: {
-        type: String,
-        observer: '_determineSuggestions',
-      },
-      _hideAutocomplete: {
-        type: Boolean,
-        value: true,
-      },
-      _index: Number,
-      _suggestions: Array,
-      // Offset makes dropdown appear below text.
-      _verticalOffset: {
-        type: Number,
-        value: 20,
-        readOnly: true,
-      },
-    },
+        code: {
+          type: Boolean,
+          value: false,
+        },
+        /** @type(?number) */
+        _colonIndex: Number,
+        _currentSearchString: {
+          type: String,
+          observer: '_determineSuggestions',
+        },
+        _hideAutocomplete: {
+          type: Boolean,
+          value: true,
+        },
+        _index: Number,
+        _suggestions: Array,
+        // Offset makes dropdown appear below text.
+        _verticalOffset: {
+          type: Number,
+          value: 20,
+          readOnly: true,
+        },
+      };
+    }
 
-    behaviors: [
-      Gerrit.FireBehavior,
-      Gerrit.KeyboardShortcutBehavior,
-    ],
-
-    keyBindings: {
-      esc: '_handleEscKey',
-      tab: '_handleEnterByKey',
-      enter: '_handleEnterByKey',
-      up: '_handleUpKey',
-      down: '_handleDownKey',
-    },
+    get keyBindings() {
+      return {
+        esc: '_handleEscKey',
+        tab: '_handleEnterByKey',
+        enter: '_handleEnterByKey',
+        up: '_handleUpKey',
+        down: '_handleDownKey',
+      };
+    }
 
     ready() {
+      super.ready();
       if (this.monospace) {
         this.classList.add('monospace');
       }
@@ -128,15 +136,15 @@
       if (this.hideBorder) {
         this.$.textarea.classList.add('noBorder');
       }
-    },
+    }
 
     closeDropdown() {
       return this.$.emojiSuggestions.close();
-    },
+    }
 
     getNativeTextarea() {
       return this.$.textarea.textarea;
-    },
+    }
 
     putCursorAtEnd() {
       const textarea = this.getNativeTextarea();
@@ -146,14 +154,14 @@
       this.async(() => {
         textarea.focus();
       });
-    },
+    }
 
     _handleEscKey(e) {
       if (this._hideAutocomplete) { return; }
       e.preventDefault();
       e.stopPropagation();
       this._resetEmojiDropdown();
-    },
+    }
 
     _handleUpKey(e) {
       if (this._hideAutocomplete) { return; }
@@ -162,7 +170,7 @@
       this.$.emojiSuggestions.cursorUp();
       this.$.textarea.textarea.focus();
       this.disableEnterKeyForSelectingEmoji = false;
-    },
+    }
 
     _handleDownKey(e) {
       if (this._hideAutocomplete) { return; }
@@ -171,7 +179,7 @@
       this.$.emojiSuggestions.cursorDown();
       this.$.textarea.textarea.focus();
       this.disableEnterKeyForSelectingEmoji = false;
-    },
+    }
 
     _handleEnterByKey(e) {
       if (this._hideAutocomplete || this.disableEnterKeyForSelectingEmoji) {
@@ -180,11 +188,11 @@
       e.preventDefault();
       e.stopPropagation();
       this._setEmoji(this.$.emojiSuggestions.getCurrentText());
-    },
+    }
 
     _handleEmojiSelect(e) {
       this._setEmoji(e.detail.selected.dataset.value);
-    },
+    }
 
     _setEmoji(text) {
       const colonIndex = this._colonIndex;
@@ -193,12 +201,13 @@
       this.$.textarea.selectionEnd = colonIndex + 1;
       this.$.reporting.reportInteraction('select-emoji');
       this._resetEmojiDropdown();
-    },
+    }
 
     _getText(value) {
       return this.text.substr(0, this._colonIndex || 0) +
           value + this.text.substr(this.$.textarea.selectionStart);
-    },
+    }
+
     /**
      * Uses a hidden element with the same width and styling of the textarea and
      * the text up until the point of interest. Then caratSpan element is added
@@ -214,17 +223,17 @@
       this.$.hiddenText.appendChild(caratSpan);
       this.$.emojiSuggestions.positionTarget = caratSpan;
       this._openEmojiDropdown();
-    },
+    }
 
     _getFontSize() {
       const fontSizePx = getComputedStyle(this).fontSize || '12px';
       return parseInt(fontSizePx.substr(0, fontSizePx.length - 2),
           10);
-    },
+    }
 
     _getScrollTop() {
       return document.body.scrollTop;
-    },
+    }
 
     /**
      * _handleKeydown used for key handling in the this.$.textarea AND all child
@@ -271,12 +280,12 @@
         this._updateCaratPosition();
       }
       this.$.textarea.textarea.focus();
-    },
+    }
 
     _openEmojiDropdown() {
       this.$.emojiSuggestions.open();
       this.$.reporting.reportInteraction('open-emoji-dropdown');
-    },
+    }
 
     _formatSuggestions(matchedSuggestions) {
       const suggestions = [];
@@ -286,7 +295,7 @@
         suggestions.push(suggestion);
       }
       this.set('_suggestions', suggestions);
-    },
+    }
 
     _determineSuggestions(emojiText) {
       if (!emojiText.length) {
@@ -299,7 +308,7 @@
         this._formatSuggestions(matches);
         this.disableEnterKeyForSelectingEmoji = false;
       }
-    },
+    }
 
     _resetEmojiDropdown() {
       // hide and reset the autocomplete dropdown.
@@ -309,11 +318,13 @@
       this.closeDropdown();
       this._colonIndex = null;
       this.$.textarea.textarea.focus();
-    },
+    }
 
     _handleTextChanged(text) {
       this.dispatchEvent(
           new CustomEvent('value-changed', {detail: {value: text}}));
-    },
-  });
+    }
+  }
+
+  customElements.define(GrTextarea.is, GrTextarea);
 })();
diff --git a/polygerrit-ui/app/elements/shared/gr-textarea/gr-textarea_test.html b/polygerrit-ui/app/elements/shared/gr-textarea/gr-textarea_test.html
index b884ecd..699deab 100644
--- a/polygerrit-ui/app/elements/shared/gr-textarea/gr-textarea_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-textarea/gr-textarea_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-textarea</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>
diff --git a/polygerrit-ui/app/elements/shared/gr-tooltip-content/gr-tooltip-content.js b/polygerrit-ui/app/elements/shared/gr-tooltip-content/gr-tooltip-content.js
index c5de8f4..3559949 100644
--- a/polygerrit-ui/app/elements/shared/gr-tooltip-content/gr-tooltip-content.js
+++ b/polygerrit-ui/app/elements/shared/gr-tooltip-content/gr-tooltip-content.js
@@ -17,31 +17,38 @@
 (function() {
   'use strict';
 
-  Polymer({
-    is: 'gr-tooltip-content',
+  /**
+    * @appliesMixin Gerrit.TooltipMixin
+    */
+  class GrTooltipContent extends Polymer.mixinBehaviors( [
+    Gerrit.TooltipBehavior,
+  ], Polymer.GestureEventListeners(
+      Polymer.LegacyElementMixin(
+          Polymer.Element))) {
+    static get is() { return 'gr-tooltip-content'; }
 
-    properties: {
-      title: {
-        type: String,
-        reflectToAttribute: true,
-      },
-      maxWidth: {
-        type: String,
-        reflectToAttribute: true,
-      },
-      positionBelow: {
-        type: Boolean,
-        valye: false,
-        reflectToAttribute: true,
-      },
-      showIcon: {
-        type: Boolean,
-        value: false,
-      },
-    },
+    static get properties() {
+      return {
+        title: {
+          type: String,
+          reflectToAttribute: true,
+        },
+        maxWidth: {
+          type: String,
+          reflectToAttribute: true,
+        },
+        positionBelow: {
+          type: Boolean,
+          valye: false,
+          reflectToAttribute: true,
+        },
+        showIcon: {
+          type: Boolean,
+          value: false,
+        },
+      };
+    }
+  }
 
-    behaviors: [
-      Gerrit.TooltipBehavior,
-    ],
-  });
+  customElements.define(GrTooltipContent.is, GrTooltipContent);
 })();
diff --git a/polygerrit-ui/app/elements/shared/gr-tooltip-content/gr-tooltip-content_test.html b/polygerrit-ui/app/elements/shared/gr-tooltip-content/gr-tooltip-content_test.html
index f9350c6..4276195 100644
--- a/polygerrit-ui/app/elements/shared/gr-tooltip-content/gr-tooltip-content_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-tooltip-content/gr-tooltip-content_test.html
@@ -17,7 +17,7 @@
 -->
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-storage</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>
diff --git a/polygerrit-ui/app/elements/shared/gr-tooltip/gr-tooltip.js b/polygerrit-ui/app/elements/shared/gr-tooltip/gr-tooltip.js
index fb87b558..170a442 100644
--- a/polygerrit-ui/app/elements/shared/gr-tooltip/gr-tooltip.js
+++ b/polygerrit-ui/app/elements/shared/gr-tooltip/gr-tooltip.js
@@ -17,23 +17,29 @@
 (function() {
   'use strict';
 
-  Polymer({
-    is: 'gr-tooltip',
+  class GrTooltip extends Polymer.GestureEventListeners(
+      Polymer.LegacyElementMixin(
+          Polymer.Element)) {
+    static get is() { return 'gr-tooltip'; }
 
-    properties: {
-      text: String,
-      maxWidth: {
-        type: String,
-        observer: '_updateWidth',
-      },
-      positionBelow: {
-        type: Boolean,
-        reflectToAttribute: true,
-      },
-    },
+    static get properties() {
+      return {
+        text: String,
+        maxWidth: {
+          type: String,
+          observer: '_updateWidth',
+        },
+        positionBelow: {
+          type: Boolean,
+          reflectToAttribute: true,
+        },
+      };
+    }
 
     _updateWidth(maxWidth) {
       this.updateStyles({'--tooltip-max-width': maxWidth});
-    },
-  });
+    }
+  }
+
+  customElements.define(GrTooltip.is, GrTooltip);
 })();
diff --git a/polygerrit-ui/app/elements/shared/gr-tooltip/gr-tooltip_test.html b/polygerrit-ui/app/elements/shared/gr-tooltip/gr-tooltip_test.html
index f59f6e1..95f3922 100644
--- a/polygerrit-ui/app/elements/shared/gr-tooltip/gr-tooltip_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-tooltip/gr-tooltip_test.html
@@ -17,7 +17,7 @@
 -->
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-storage</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>
diff --git a/polygerrit-ui/app/elements/shared/revision-info/revision-info_test.html b/polygerrit-ui/app/elements/shared/revision-info/revision-info_test.html
index 7e5810b..187ce35 100644
--- a/polygerrit-ui/app/elements/shared/revision-info/revision-info_test.html
+++ b/polygerrit-ui/app/elements/shared/revision-info/revision-info_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>revision-info</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>
diff --git a/polygerrit-ui/app/embed/embed_test.html b/polygerrit-ui/app/embed/embed_test.html
index 048dffa..5d81b7e 100644
--- a/polygerrit-ui/app/embed/embed_test.html
+++ b/polygerrit-ui/app/embed/embed_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>embed_test</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>
diff --git a/polygerrit-ui/app/polylint_test.sh b/polygerrit-ui/app/polylint_test.sh
index 54b31d3..98cf06f 100755
--- a/polygerrit-ui/app/polylint_test.sh
+++ b/polygerrit-ui/app/polylint_test.sh
@@ -25,4 +25,7 @@
 
 unzip -o polygerrit-ui/polygerrit_components.bower_components.zip -d polygerrit-ui/app
 
-$polymer_bin lint --root polygerrit-ui/app --entrypoint polygerrit-ui/app/elements/gr-app.html --component-dir 'polygerrit-ui/app/bower_components' --verbose --rules polymer-2-hybrid --sources "polygerrit-ui/app/**/*"
+#Can't use --root with polymer.json - see https://github.com/Polymer/tools/issues/2616
+#Change current directory to the root folder
+cd polygerrit-ui/app
+$polymer_bin lint --component-dir 'bower_components' --verbose
diff --git a/polygerrit-ui/app/polymer.json b/polygerrit-ui/app/polymer.json
new file mode 100644
index 0000000..411c969
--- /dev/null
+++ b/polygerrit-ui/app/polymer.json
@@ -0,0 +1,14 @@
+{
+  "entrypoint": "elements/gr-app.html",
+  "sources": [
+    "behaviors/**/*",
+    "elements/**/*",
+    "scripts/**/*",
+    "styles/*",
+    "types/**/*"
+  ],
+  "lint": {
+    "rules": ["polymer-2"],
+    "ignoreWarnings": ["deprecated-dom-call"]
+  }
+}
diff --git a/polygerrit-ui/app/samples/coverage-plugin.html b/polygerrit-ui/app/samples/coverage-plugin.html
index e4c23aa..fa44a47 100644
--- a/polygerrit-ui/app/samples/coverage-plugin.html
+++ b/polygerrit-ui/app/samples/coverage-plugin.html
@@ -60,6 +60,8 @@
       }).enableToggleCheckbox('Display Coverage', checkbox => {
         // Checkbox is attached so now add the notifier that will be controlled
         // by the checkbox.
+        // Checkbox will only be added to the file diff page, in the top right
+        // section near the "Diff view".
         annotationApi.addNotifier(notifyFunc => {
           new Promise(resolve => setTimeout(resolve, 3000)).then(() => {
             populateWithDummyData(coverageData);
diff --git a/polygerrit-ui/app/scripts/gr-display-name-utils/gr-display-name-utils_test.html b/polygerrit-ui/app/scripts/gr-display-name-utils/gr-display-name-utils_test.html
index b04e324..a35e034 100644
--- a/polygerrit-ui/app/scripts/gr-display-name-utils/gr-display-name-utils_test.html
+++ b/polygerrit-ui/app/scripts/gr-display-name-utils/gr-display-name-utils_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-display-name-utils</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>
diff --git a/polygerrit-ui/app/scripts/gr-email-suggestions-provider/gr-email-suggestions-provider_test.html b/polygerrit-ui/app/scripts/gr-email-suggestions-provider/gr-email-suggestions-provider_test.html
index 0266ab9..ac32efa 100644
--- a/polygerrit-ui/app/scripts/gr-email-suggestions-provider/gr-email-suggestions-provider_test.html
+++ b/polygerrit-ui/app/scripts/gr-email-suggestions-provider/gr-email-suggestions-provider_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-email-suggestions-provider</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>
diff --git a/polygerrit-ui/app/scripts/gr-group-suggestions-provider/gr-group-suggestions-provider_test.html b/polygerrit-ui/app/scripts/gr-group-suggestions-provider/gr-group-suggestions-provider_test.html
index b60aaa9..6908256 100644
--- a/polygerrit-ui/app/scripts/gr-group-suggestions-provider/gr-group-suggestions-provider_test.html
+++ b/polygerrit-ui/app/scripts/gr-group-suggestions-provider/gr-group-suggestions-provider_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-group-suggestions-provider</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>
diff --git a/polygerrit-ui/app/scripts/gr-reviewer-suggestions-provider/gr-reviewer-suggestions-provider_test.html b/polygerrit-ui/app/scripts/gr-reviewer-suggestions-provider/gr-reviewer-suggestions-provider_test.html
index ca3c277..6fcc4dc 100644
--- a/polygerrit-ui/app/scripts/gr-reviewer-suggestions-provider/gr-reviewer-suggestions-provider_test.html
+++ b/polygerrit-ui/app/scripts/gr-reviewer-suggestions-provider/gr-reviewer-suggestions-provider_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-reviewer-suggestions-provider</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>
diff --git a/polygerrit-ui/app/styles/themes/app-theme.html b/polygerrit-ui/app/styles/themes/app-theme.html
index 41395a3..ccdde2b 100644
--- a/polygerrit-ui/app/styles/themes/app-theme.html
+++ b/polygerrit-ui/app/styles/themes/app-theme.html
@@ -14,8 +14,6 @@
 See the License for the specific language governing permissions and
 limitations under the License.
 -->
-<link rel="import" href="/bower_components/polymer/lib/elements/custom-style.html">
-
 <custom-style><style is="custom-style">
 html {
   /**
diff --git a/polygerrit-ui/app/styles/themes/dark-theme.html b/polygerrit-ui/app/styles/themes/dark-theme.html
index 69a787e..f3d5e78 100644
--- a/polygerrit-ui/app/styles/themes/dark-theme.html
+++ b/polygerrit-ui/app/styles/themes/dark-theme.html
@@ -14,8 +14,6 @@
 See the License for the specific language governing permissions and
 limitations under the License.
 -->
-<link rel="import" href="/bower_components/polymer/lib/elements/custom-style.html">
-
 <dom-module id="dark-theme">
   <custom-style><style is="custom-style">
     html {
diff --git a/polygerrit-ui/app/template_test.sh b/polygerrit-ui/app/template_test.sh
index b1a2380..ccefd83 100755
--- a/polygerrit-ui/app/template_test.sh
+++ b/polygerrit-ui/app/template_test.sh
@@ -1,5 +1,11 @@
 #!/bin/bash
 
+# TODO(dmfilippov): Update template_test to support Polymer 2/Polymer 3 or delete it completely
+# The following line temporary disable template tests. Existing implementation doesn't compatible
+# with Polymer 2 & 3 class-based components. Polymer linter makes some checks regarding
+# templates and binding, but not all.
+exit 0
+
 set -ex
 
 node_bin=$(which node)
diff --git a/polygerrit-ui/app/test/common-test-setup.js b/polygerrit-ui/app/test/common-test-setup.js
deleted file mode 100644
index 7ceff7e..0000000
--- a/polygerrit-ui/app/test/common-test-setup.js
+++ /dev/null
@@ -1,26 +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.
- */
-
-/**
- * Helps looking up the proper iron-input element during the Polymer 2
- * transition. Polymer 2 uses the <iron-input> element, while Polymer 1 uses
- * the nested <input is="iron-input"> element.
- */
-window.ironInput = function(element) {
-  return Polymer.dom(element).querySelector(
-      Polymer.Element ? 'iron-input' : 'input[is=iron-input]');
-};
diff --git a/polygerrit-ui/app/types/polymer-behaviors.js b/polygerrit-ui/app/types/polymer-behaviors.js
new file mode 100644
index 0000000..18e7ad9
--- /dev/null
+++ b/polygerrit-ui/app/types/polymer-behaviors.js
@@ -0,0 +1,52 @@
+/**
+ * @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.
+ */
+
+/**
+ * For the purposes of template type checking, externs should be added for
+ * anything set on the window object. Note that sub-properties of these
+ * declared properties are considered something separate.
+ *
+ * This file is only for template type checking, not used in Gerrit code.
+ */
+
+/* eslint-disable no-var */
+/* eslint-disable no-unused-vars */
+
+function PolymerMixins() {
+  // This function must not be called.
+  // Due to an issue in polymer linter the linter can't
+  // process correctly some behaviors from Polymer library.
+  // To workaround this issue, here we define a minimal mixin to allow
+  // linter process our code correctly. You can add more properties to mixins
+  // if needed.
+
+  // Important! Use mixins from these file only inside JSDoc comments.
+  // Do not use it in the real code
+
+  /**
+   * @polymer
+   * @mixinFunction
+   * */
+  Polymer.IronFitMixin = base =>
+    class extends base {
+      static get properties() {
+        return {
+          positionTarget: Object,
+        };
+      }
+    };
+}
diff --git a/tools/polygerrit-updater/.gitignore b/tools/polygerrit-updater/.gitignore
new file mode 100644
index 0000000..8619a37
--- /dev/null
+++ b/tools/polygerrit-updater/.gitignore
@@ -0,0 +1,3 @@
+/.idea/
+/node_modules/
+/js/
\ No newline at end of file
diff --git a/tools/polygerrit-updater/package-lock.json b/tools/polygerrit-updater/package-lock.json
new file mode 100644
index 0000000..9256997
--- /dev/null
+++ b/tools/polygerrit-updater/package-lock.json
@@ -0,0 +1,18 @@
+{
+  "name": "polygerrit-updater",
+  "version": "1.0.0",
+  "lockfileVersion": 1,
+  "requires": true,
+  "dependencies": {
+    "@types/node": {
+      "version": "12.7.12",
+      "resolved": "https://registry.npmjs.org/@types/node/-/node-12.7.12.tgz",
+      "integrity": "sha512-KPYGmfD0/b1eXurQ59fXD1GBzhSQfz6/lKBxkaHX9dKTzjXbK68Zt7yGUxUsCS1jeTy/8aL+d9JEr+S54mpkWQ=="
+    },
+    "typescript": {
+      "version": "3.6.4",
+      "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.6.4.tgz",
+      "integrity": "sha512-unoCll1+l+YK4i4F8f22TaNVPRHcD9PA3yCuZ8g5e0qGqlVlJ/8FSateOLLSagn+Yg5+ZwuPkL8LFUc0Jcvksg=="
+    }
+  }
+}
diff --git a/tools/polygerrit-updater/package.json b/tools/polygerrit-updater/package.json
new file mode 100644
index 0000000..3609dad
--- /dev/null
+++ b/tools/polygerrit-updater/package.json
@@ -0,0 +1,15 @@
+{
+  "name": "polygerrit-updater",
+  "version": "1.0.0",
+  "description": "Polygerrit source code updater",
+  "scripts": {
+    "compile": "tsc",
+    "convert": "npm run compile && node js/src/index.js"
+  },
+  "author": "",
+  "license": "Apache-2.0",
+  "dependencies": {
+    "@types/node": "^12.7.12",
+    "typescript": "^3.6.4"
+  }
+}
diff --git a/tools/polygerrit-updater/readme.txt b/tools/polygerrit-updater/readme.txt
new file mode 100644
index 0000000..2b2cea8
--- /dev/null
+++ b/tools/polygerrit-updater/readme.txt
@@ -0,0 +1,56 @@
+This folder contains tool to update Polymer components to class based components.
+This is a temporary tools, it will be removed in a few weeks.
+
+How to use this tool: initial steps
+1) Important - Commit and push all your changes. Otherwise, you can loose you work.
+
+2) Ensure, that tools/polygerrit-updater is your current directory
+
+3) Run
+npm install
+
+4) If you want to convert the whole project, run
+npm run convert -- --i \
+  --root ../../polygerrit-ui --src app/elements --r \
+  --exclude app/elements/core/gr-reporting/gr-reporting.js \
+     app/elements/diff/gr-comment-api/gr-comment-api-mock.js \
+     app/elements/plugins/gr-dom-hooks/gr-dom-hooks.js
+
+You can convert only specific files (can be useful if you want to convert some files in your change)
+npm run convert -- --i \
+  --root ../../polygerrit-ui
+  --src app/elements/file1.js \
+      app/elements/folder/file2.js
+
+4) Search for the following string in all .js files:
+//This file has the following problems with comments:
+
+If you find such string in a .js file - you must manually fix comments in this file.
+(It is expected that you shouldn't have such problems)
+
+5) Go to the gerrit root folder and run
+npm run eslintfix
+
+(If you are doing it for the first time, run the following command before in gerrit root folder:
+npm run install)
+
+Fix error after eslintfix (if exists)
+
+6) If you are doing conversion for the whole project, make the followin changes:
+
+a) Add
+<link rel="import" href="../../../types/polymer-behaviors.js">
+to
+polygerrit-ui/app/elements/shared/gr-autocomplete-dropdown/gr-autocomplete-dropdown.html
+
+b) Update polymer.json with the following rules:
+  "lint": {
+    "rules": ["polymer-2"],
+    "ignoreWarnings": ["deprecated-dom-call"]
+  }
+
+
+
+5) Commit changed files.
+
+6) You can update excluded files later.
diff --git a/tools/polygerrit-updater/src/funcToClassConversion/funcToClassBasedElementConverter.ts b/tools/polygerrit-updater/src/funcToClassConversion/funcToClassBasedElementConverter.ts
new file mode 100644
index 0000000..b92a6e9
--- /dev/null
+++ b/tools/polygerrit-updater/src/funcToClassConversion/funcToClassBasedElementConverter.ts
@@ -0,0 +1,131 @@
+// 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.
+
+import {LegacyLifecycleMethodsArray, LegacyPolymerComponent} from './polymerComponentParser';
+import {LifecycleMethodsBuilder} from './lifecycleMethodsBuilder';
+import {ClassBasedPolymerElement, PolymerElementBuilder} from './polymerElementBuilder';
+import * as codeUtils from '../utils/codeUtils';
+import * as ts from 'typescript';
+
+export class PolymerFuncToClassBasedConverter {
+  public static convert(component: LegacyPolymerComponent): ClassBasedPolymerElement {
+    const legacySettings = component.componentSettings;
+    const reservedDeclarations = legacySettings.reservedDeclarations;
+
+    if(!reservedDeclarations.is) {
+      throw new Error("Legacy component doesn't have 'is' property");
+    }
+    const className = this.generateClassNameFromTagName(reservedDeclarations.is.data);
+    const updater = new PolymerElementBuilder(component, className);
+    updater.addIsAccessor(reservedDeclarations.is.data);
+
+    if(reservedDeclarations.properties) {
+      updater.addPolymerPropertiesAccessor(reservedDeclarations.properties);
+    }
+
+    updater.addMixin("Polymer.Element");
+    updater.addMixin("Polymer.LegacyElementMixin");
+    updater.addMixin("Polymer.GestureEventListeners");
+
+    if(reservedDeclarations._legacyUndefinedCheck) {
+      updater.addMixin("Polymer.LegacyDataMixin");
+    }
+
+    if(reservedDeclarations.behaviors) {
+      updater.addMixin("Polymer.mixinBehaviors", [reservedDeclarations.behaviors.data]);
+      const mixinNames = this.getMixinNamesFromBehaviors(reservedDeclarations.behaviors.data);
+      const jsDocLines = mixinNames.map(mixinName => {
+        return `@appliesMixin ${mixinName}`;
+      });
+      updater.addClassJSDocComments(jsDocLines);
+    }
+
+    if(reservedDeclarations.observers) {
+      updater.addPolymerPropertiesObservers(reservedDeclarations.observers.data);
+    }
+
+    if(reservedDeclarations.keyBindings) {
+      updater.addKeyBindings(reservedDeclarations.keyBindings.data);
+    }
+
+
+    const lifecycleBuilder = new LifecycleMethodsBuilder();
+    if (reservedDeclarations.listeners) {
+      lifecycleBuilder.addListeners(reservedDeclarations.listeners.data, legacySettings.ordinaryMethods);
+    }
+
+    if (reservedDeclarations.hostAttributes) {
+      lifecycleBuilder.addHostAttributes(reservedDeclarations.hostAttributes.data);
+    }
+
+    for(const name of LegacyLifecycleMethodsArray) {
+      const existingMethod = legacySettings.lifecycleMethods.get(name);
+      if(existingMethod) {
+        lifecycleBuilder.addLegacyLifecycleMethod(name, existingMethod)
+      }
+    }
+
+    const newLifecycleMethods = lifecycleBuilder.buildNewMethods();
+    updater.addLifecycleMethods(newLifecycleMethods);
+
+
+    updater.addOrdinaryMethods(legacySettings.ordinaryMethods);
+    updater.addOrdinaryGetAccessors(legacySettings.ordinaryGetAccessors);
+    updater.addOrdinaryShorthandProperties(legacySettings.ordinaryShorthandProperties);
+    updater.addOrdinaryPropertyAssignments(legacySettings.ordinaryPropertyAssignments);
+
+    return updater.build();
+  }
+
+  private static generateClassNameFromTagName(tagName: string) {
+    let result = "";
+    let nextUppercase = true;
+    for(const ch of tagName) {
+      if (ch === '-') {
+        nextUppercase = true;
+        continue;
+      }
+      result += nextUppercase ? ch.toUpperCase() : ch;
+      nextUppercase = false;
+    }
+    return result;
+  }
+
+  private static getMixinNamesFromBehaviors(behaviors: ts.ArrayLiteralExpression): string[] {
+    return behaviors.elements.map((expression) => {
+      const propertyAccessExpression = codeUtils.assertNodeKind(expression, ts.SyntaxKind.PropertyAccessExpression) as ts.PropertyAccessExpression;
+      const namespaceName = codeUtils.assertNodeKind(propertyAccessExpression.expression, ts.SyntaxKind.Identifier) as ts.Identifier;
+      const behaviorName = propertyAccessExpression.name;
+      if(namespaceName.text === 'Gerrit') {
+        let behaviorNameText = behaviorName.text;
+        const suffix = 'Behavior';
+        if(behaviorNameText.endsWith(suffix)) {
+          behaviorNameText =
+              behaviorNameText.substr(0, behaviorNameText.length - suffix.length);
+        }
+        const mixinName = behaviorNameText + 'Mixin';
+        return `${namespaceName.text}.${mixinName}`
+      } else if(namespaceName.text === 'Polymer') {
+        let behaviorNameText = behaviorName.text;
+        if(behaviorNameText === "IronFitBehavior") {
+          return "Polymer.IronFitMixin";
+        } else if(behaviorNameText === "IronOverlayBehavior") {
+          return "";
+        }
+        throw new Error(`Unsupported behavior: ${propertyAccessExpression.getText()}`);
+      }
+      throw new Error(`Unsupported behavior name ${expression.getFullText()}`)
+    }).filter(name => name.length > 0);
+  }
+}
diff --git a/tools/polygerrit-updater/src/funcToClassConversion/legacyPolymerFuncReplacer.ts b/tools/polygerrit-updater/src/funcToClassConversion/legacyPolymerFuncReplacer.ts
new file mode 100644
index 0000000..57b7b8d
--- /dev/null
+++ b/tools/polygerrit-updater/src/funcToClassConversion/legacyPolymerFuncReplacer.ts
@@ -0,0 +1,74 @@
+// 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.
+
+import * as ts from 'typescript';
+import * as codeUtils from '../utils/codeUtils'
+import {LegacyPolymerComponent} from './polymerComponentParser';
+import {ClassBasedPolymerElement} from './polymerElementBuilder';
+
+export class LegacyPolymerFuncReplaceResult {
+  public constructor(
+      private readonly transformationResult: ts.TransformationResult<ts.SourceFile>,
+      public readonly leadingComments: string[]) {
+  }
+  public get file(): ts.SourceFile {
+    return this.transformationResult.transformed[0];
+  }
+  public dispose() {
+    this.transformationResult.dispose();
+  }
+
+}
+
+export class LegacyPolymerFuncReplacer {
+  private readonly callStatement: ts.ExpressionStatement;
+  private readonly parentBlock: ts.Block;
+  private readonly callStatementIndexInBlock: number;
+  public constructor(private readonly legacyComponent: LegacyPolymerComponent) {
+    this.callStatement = codeUtils.assertNodeKind(legacyComponent.polymerFuncCallExpr.parent, ts.SyntaxKind.ExpressionStatement);
+    this.parentBlock = codeUtils.assertNodeKind(this.callStatement.parent, ts.SyntaxKind.Block);
+    this.callStatementIndexInBlock = this.parentBlock.statements.indexOf(this.callStatement);
+    if(this.callStatementIndexInBlock < 0) {
+      throw new Error("Internal error! Couldn't find statement in its own parent");
+    }
+  }
+  public replace(classBasedElement: ClassBasedPolymerElement): LegacyPolymerFuncReplaceResult {
+    const classDeclarationWithComments = this.appendLeadingCommentToClassDeclaration(classBasedElement.classDeclaration);
+    return new LegacyPolymerFuncReplaceResult(
+        this.replaceLegacyPolymerFunction(classDeclarationWithComments.classDeclarationWithCommentsPlaceholder, classBasedElement.componentRegistration),
+        classDeclarationWithComments.leadingComments);
+  }
+  private appendLeadingCommentToClassDeclaration(classDeclaration: ts.ClassDeclaration): {classDeclarationWithCommentsPlaceholder: ts.ClassDeclaration, leadingComments: string[]} {
+    const text = this.callStatement.getFullText();
+    let classDeclarationWithCommentsPlaceholder = classDeclaration;
+    const leadingComments: string[] = [];
+    ts.forEachLeadingCommentRange(text, 0, (pos, end, kind, hasTrailingNewLine) => {
+      classDeclarationWithCommentsPlaceholder = codeUtils.addReplacableCommentBeforeNode(classDeclarationWithCommentsPlaceholder, String(leadingComments.length));
+      leadingComments.push(text.substring(pos, end));
+    });
+    return {
+      classDeclarationWithCommentsPlaceholder: classDeclarationWithCommentsPlaceholder,
+      leadingComments: leadingComments
+    }
+  }
+  private replaceLegacyPolymerFunction(classDeclaration: ts.ClassDeclaration, componentRegistration: ts.ExpressionStatement): ts.TransformationResult<ts.SourceFile> {
+    const newStatements = Array.from(this.parentBlock.statements);
+    newStatements.splice(this.callStatementIndexInBlock, 1, classDeclaration, componentRegistration);
+
+    const updatedBlock = ts.getMutableClone(this.parentBlock);
+    updatedBlock.statements = ts.createNodeArray(newStatements);
+    return codeUtils.replaceNode(this.legacyComponent.parsedFile, this.parentBlock, updatedBlock);
+
+  }
+}
\ No newline at end of file
diff --git a/tools/polygerrit-updater/src/funcToClassConversion/lifecycleMethodsBuilder.ts b/tools/polygerrit-updater/src/funcToClassConversion/lifecycleMethodsBuilder.ts
new file mode 100644
index 0000000..e9e13f5
--- /dev/null
+++ b/tools/polygerrit-updater/src/funcToClassConversion/lifecycleMethodsBuilder.ts
@@ -0,0 +1,140 @@
+// 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.
+
+import * as ts from 'typescript';
+import * as codeUtils from '../utils/codeUtils';
+import {LegacyLifecycleMethodName, OrdinaryMethods} from './polymerComponentParser';
+
+interface LegacyLifecycleMethodContent {
+  codeAtMethodStart: ts.Statement[];
+  existingMethod?: ts.MethodDeclaration;
+  codeAtMethodEnd: ts.Statement[];
+}
+
+export interface LifecycleMethod {
+  originalPos: number;//-1 - no original method exists
+  method: ts.MethodDeclaration;
+  name: LegacyLifecycleMethodName;
+}
+
+export class LifecycleMethodsBuilder {
+  private readonly methods: Map<LegacyLifecycleMethodName, LegacyLifecycleMethodContent> = new Map();
+
+  private getMethodContent(name: LegacyLifecycleMethodName): LegacyLifecycleMethodContent {
+    if(!this.methods.has(name)) {
+      this.methods.set(name, {
+        codeAtMethodStart: [],
+        codeAtMethodEnd: []
+      });
+    }
+    return this.methods.get(name)!;
+  }
+
+  public addListeners(legacyListeners: ts.ObjectLiteralExpression, legacyOrdinaryMethods: OrdinaryMethods) {
+    for(const listener of legacyListeners.properties) {
+      const propertyAssignment = codeUtils.assertNodeKind(listener, ts.SyntaxKind.PropertyAssignment) as ts.PropertyAssignment;
+      if(!propertyAssignment.name) {
+        throw new Error("Listener must have event name");
+      }
+      let eventNameLiteral: ts.StringLiteral;
+      let commentsToRestore: string[] = [];
+      if(propertyAssignment.name.kind === ts.SyntaxKind.StringLiteral) {
+        //We don't loose comment in this case, because we keep literal as is
+        eventNameLiteral = propertyAssignment.name;
+      } else if(propertyAssignment.name.kind === ts.SyntaxKind.Identifier) {
+        eventNameLiteral = ts.createStringLiteral(propertyAssignment.name.text);
+        commentsToRestore = codeUtils.getLeadingComments(propertyAssignment);
+      } else {
+        throw new Error(`Unsupported type ${ts.SyntaxKind[propertyAssignment.name.kind]}`);
+      }
+
+      const handlerLiteral = codeUtils.assertNodeKind(propertyAssignment.initializer, ts.SyntaxKind.StringLiteral) as ts.StringLiteral;
+      const handlerImpl = legacyOrdinaryMethods.get(handlerLiteral.text);
+      if(!handlerImpl) {
+        throw new Error(`Can't find event handler '${handlerLiteral.text}'`);
+      }
+      const eventHandlerAccess = ts.createPropertyAccess(ts.createThis(), handlerLiteral.text);
+      //ts.forEachChild(handler)
+      const args: ts.Identifier[] = handlerImpl.parameters.map((arg) => codeUtils.assertNodeKind(arg.name, ts.SyntaxKind.Identifier));
+      const eventHandlerCall = ts.createCall(eventHandlerAccess, [], args);
+      let arrowFunc = ts.createArrowFunction([], [], handlerImpl.parameters, undefined, undefined, eventHandlerCall);
+      arrowFunc = codeUtils.addNewLineBeforeNode(arrowFunc);
+
+      const methodContent = this.getMethodContent("created");
+      //See https://polymer-library.polymer-project.org/3.0/docs/devguide/gesture-events for a list of events
+      if(["down", "up", "tap", "track"].indexOf(eventNameLiteral.text) >= 0) {
+        const methodCall = ts.createCall(codeUtils.createNameExpression("Polymer.Gestures.addListener"), [], [ts.createThis(), eventNameLiteral, arrowFunc]);
+        methodContent.codeAtMethodEnd.push(ts.createExpressionStatement(methodCall));
+      }
+      else {
+        let methodCall = ts.createCall(ts.createPropertyAccess(ts.createThis(), "addEventListener"), [], [eventNameLiteral, arrowFunc]);
+        methodCall = codeUtils.restoreLeadingComments(methodCall, commentsToRestore);
+        methodContent.codeAtMethodEnd.push(ts.createExpressionStatement(methodCall));
+      }
+    }
+  }
+
+  public addHostAttributes(legacyHostAttributes: ts.ObjectLiteralExpression) {
+    for(const listener of legacyHostAttributes.properties) {
+      const propertyAssignment = codeUtils.assertNodeKind(listener, ts.SyntaxKind.PropertyAssignment) as ts.PropertyAssignment;
+      if(!propertyAssignment.name) {
+        throw new Error("Listener must have event name");
+      }
+      let attributeNameLiteral: ts.StringLiteral;
+      if(propertyAssignment.name.kind === ts.SyntaxKind.StringLiteral) {
+        attributeNameLiteral = propertyAssignment.name;
+      } else if(propertyAssignment.name.kind === ts.SyntaxKind.Identifier) {
+        attributeNameLiteral = ts.createStringLiteral(propertyAssignment.name.text);
+      } else {
+        throw new Error(`Unsupported type ${ts.SyntaxKind[propertyAssignment.name.kind]}`);
+      }
+      let attributeValueLiteral: ts.StringLiteral | ts.NumericLiteral;
+      if(propertyAssignment.initializer.kind === ts.SyntaxKind.StringLiteral) {
+        attributeValueLiteral = propertyAssignment.initializer as ts.StringLiteral;
+      } else if(propertyAssignment.initializer.kind === ts.SyntaxKind.NumericLiteral) {
+        attributeValueLiteral = propertyAssignment.initializer as ts.NumericLiteral;
+      } else {
+        throw new Error(`Unsupported type ${ts.SyntaxKind[propertyAssignment.initializer.kind]}`);
+      }
+      const methodCall = ts.createCall(ts.createPropertyAccess(ts.createThis(), "_ensureAttribute"), [], [attributeNameLiteral, attributeValueLiteral]);
+      this.getMethodContent("ready").codeAtMethodEnd.push(ts.createExpressionStatement(methodCall));
+    }
+  }
+
+  public addLegacyLifecycleMethod(name: LegacyLifecycleMethodName, method: ts.MethodDeclaration) {
+    const content = this.getMethodContent(name);
+    if(content.existingMethod) {
+      throw new Error(`Legacy lifecycle method ${name} already added`);
+    }
+    content.existingMethod = method;
+  }
+
+  public buildNewMethods(): LifecycleMethod[] {
+    const result = [];
+    for(const [name, content] of this.methods) {
+      const newMethod = this.createLifecycleMethod(name, content.existingMethod, content.codeAtMethodStart, content.codeAtMethodEnd);
+      if(!newMethod) continue;
+      result.push({
+        name,
+        originalPos: content.existingMethod ? content.existingMethod.pos : -1,
+        method: newMethod
+      })
+    }
+    return result;
+  }
+
+  private createLifecycleMethod(name: string, methodDecl: ts.MethodDeclaration | undefined, codeAtStart: ts.Statement[], codeAtEnd: ts.Statement[]): ts.MethodDeclaration | undefined {
+    return codeUtils.createMethod(name, methodDecl, codeAtStart, codeAtEnd, true);
+  }
+}
\ No newline at end of file
diff --git a/tools/polygerrit-updater/src/funcToClassConversion/polymerComponentParser.ts b/tools/polygerrit-updater/src/funcToClassConversion/polymerComponentParser.ts
new file mode 100644
index 0000000..6006608
--- /dev/null
+++ b/tools/polygerrit-updater/src/funcToClassConversion/polymerComponentParser.ts
@@ -0,0 +1,301 @@
+// 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.
+
+import * as ts from "typescript";
+import * as fs from "fs";
+import * as path from "path";
+import { unexpectedValue } from "../utils/unexpectedValue";
+import * as codeUtils from "../utils/codeUtils";
+import {CommentsParser} from '../utils/commentsParser';
+
+export class LegacyPolymerComponentParser {
+  public constructor(private readonly rootDir: string, private readonly htmlFiles: Set<string>) {
+  }
+  public async parse(jsFile: string): Promise<ParsedPolymerComponent | null> {
+    const sourceFile: ts.SourceFile  = this.parseJsFile(jsFile);
+    const legacyComponent = this.tryParseLegacyComponent(sourceFile);
+    if (legacyComponent) {
+      return legacyComponent;
+    }
+    return null;
+  }
+  private parseJsFile(jsFile: string): ts.SourceFile {
+    return ts.createSourceFile(jsFile, fs.readFileSync(path.resolve(this.rootDir, jsFile)).toString(), ts.ScriptTarget.ES2015, true);
+  }
+
+  private tryParseLegacyComponent(sourceFile: ts.SourceFile): ParsedPolymerComponent | null {
+    const polymerFuncCalls: ts.CallExpression[] = [];
+
+    function addPolymerFuncCall(node: ts.Node) {
+      if(node.kind === ts.SyntaxKind.CallExpression) {
+        const callExpression: ts.CallExpression = node as ts.CallExpression;
+        if(callExpression.expression.kind === ts.SyntaxKind.Identifier) {
+          const identifier = callExpression.expression as ts.Identifier;
+          if(identifier.text === "Polymer") {
+            polymerFuncCalls.push(callExpression);
+          }
+        }
+      }
+      ts.forEachChild(node, addPolymerFuncCall);
+    }
+
+    addPolymerFuncCall(sourceFile);
+
+
+    if (polymerFuncCalls.length === 0) {
+      return null;
+    }
+    if (polymerFuncCalls.length > 1) {
+      throw new Error("Each .js file must contain only one Polymer component");
+    }
+    const parsedPath = path.parse(sourceFile.fileName);
+    const htmlFullPath = path.format({
+      dir: parsedPath.dir,
+      name: parsedPath.name,
+      ext: ".html"
+    });
+    if (!this.htmlFiles.has(htmlFullPath)) {
+      throw new Error("Legacy .js component dosn't have associated .html file");
+    }
+
+    const polymerFuncCall = polymerFuncCalls[0];
+    if(polymerFuncCall.arguments.length !== 1) {
+      throw new Error("The Polymer function must be called with exactly one parameter");
+    }
+    const argument = polymerFuncCall.arguments[0];
+    if(argument.kind !== ts.SyntaxKind.ObjectLiteralExpression) {
+      throw new Error("The parameter for Polymer function must be ObjectLiteralExpression (i.e. '{...}')");
+    }
+    const infoArg = argument as ts.ObjectLiteralExpression;
+
+    return {
+      jsFile: sourceFile.fileName,
+      htmlFile: htmlFullPath,
+      parsedFile: sourceFile,
+      polymerFuncCallExpr: polymerFuncCalls[0],
+      componentSettings: this.parseLegacyComponentSettings(infoArg),
+    };
+  }
+
+  private parseLegacyComponentSettings(info: ts.ObjectLiteralExpression): LegacyPolymerComponentSettings {
+    const props: Map<string, ts.ObjectLiteralElementLike> = new Map();
+    for(const property of info.properties) {
+      const name = property.name;
+      if (name === undefined) {
+        throw new Error("Property name is not defined");
+      }
+      switch(name.kind) {
+        case ts.SyntaxKind.Identifier:
+        case ts.SyntaxKind.StringLiteral:
+          if (props.has(name.text)) {
+            throw new Error(`Property ${name.text} appears more than once`);
+          }
+          props.set(name.text, property);
+          break;
+        case ts.SyntaxKind.ComputedPropertyName:
+          continue;
+        default:
+          unexpectedValue(ts.SyntaxKind[name.kind]);
+      }
+    }
+
+    if(props.has("_noAccessors")) {
+      throw new Error("_noAccessors is not supported");
+    }
+
+    const legacyLifecycleMethods: LegacyLifecycleMethods = new Map();
+    for(const name of LegacyLifecycleMethodsArray) {
+      const methodDecl = this.getLegacyMethodDeclaration(props, name);
+      if(methodDecl) {
+        legacyLifecycleMethods.set(name, methodDecl);
+      }
+    }
+
+    const ordinaryMethods: OrdinaryMethods = new Map();
+    const ordinaryShorthandProperties: OrdinaryShorthandProperties = new Map();
+    const ordinaryGetAccessors: OrdinaryGetAccessors = new Map();
+    const ordinaryPropertyAssignments: OrdinaryPropertyAssignments = new Map();
+    for(const [name, val] of props) {
+      if(RESERVED_NAMES.hasOwnProperty(name)) continue;
+      switch(val.kind) {
+        case ts.SyntaxKind.MethodDeclaration:
+          ordinaryMethods.set(name, val as ts.MethodDeclaration);
+          break;
+        case ts.SyntaxKind.ShorthandPropertyAssignment:
+          ordinaryShorthandProperties.set(name, val as ts.ShorthandPropertyAssignment);
+          break;
+        case ts.SyntaxKind.GetAccessor:
+          ordinaryGetAccessors.set(name, val as ts.GetAccessorDeclaration);
+          break;
+        case ts.SyntaxKind.PropertyAssignment:
+          ordinaryPropertyAssignments.set(name, val as ts.PropertyAssignment);
+          break;
+        default:
+          throw new Error(`Unsupported element kind: ${ts.SyntaxKind[val.kind]}`);
+      }
+      //ordinaryMethods.set(name, tsUtils.assertNodeKind(val, ts.SyntaxKind.MethodDeclaration) as ts.MethodDeclaration);
+    }
+
+    const eventsComments: string[] = this.getEventsComments(info.getFullText());
+
+    return {
+      reservedDeclarations: {
+        is: this.getStringLiteralValueWithComments(this.getLegacyPropertyInitializer(props, "is")),
+        _legacyUndefinedCheck: this.getBooleanLiteralValueWithComments(this.getLegacyPropertyInitializer(props, "_legacyUndefinedCheck")),
+        properties: this.getObjectLiteralExpressionWithComments(this.getLegacyPropertyInitializer(props, "properties")),
+        behaviors: this.getArrayLiteralExpressionWithComments(this.getLegacyPropertyInitializer(props, "behaviors")),
+        observers: this.getArrayLiteralExpressionWithComments(this.getLegacyPropertyInitializer(props, "observers")),
+        listeners: this.getObjectLiteralExpressionWithComments(this.getLegacyPropertyInitializer(props, "listeners")),
+        hostAttributes: this.getObjectLiteralExpressionWithComments(this.getLegacyPropertyInitializer(props, "hostAttributes")),
+        keyBindings: this.getObjectLiteralExpressionWithComments(this.getLegacyPropertyInitializer(props, "keyBindings")),
+      },
+      eventsComments: eventsComments,
+      lifecycleMethods: legacyLifecycleMethods,
+      ordinaryMethods: ordinaryMethods,
+      ordinaryShorthandProperties: ordinaryShorthandProperties,
+      ordinaryGetAccessors: ordinaryGetAccessors,
+      ordinaryPropertyAssignments: ordinaryPropertyAssignments,
+    };
+  }
+
+  private convertLegacyProeprtyInitializer<T>(initializer: LegacyPropertyInitializer | undefined, converter: (exp: ts.Expression) => T): DataWithComments<T> | undefined {
+    if(!initializer) {
+      return undefined;
+    }
+    return {
+      data: converter(initializer.data),
+      leadingComments: initializer.leadingComments,
+    }
+  }
+
+  private getObjectLiteralExpressionWithComments(initializer: LegacyPropertyInitializer | undefined): DataWithComments<ts.ObjectLiteralExpression> | undefined {
+    return this.convertLegacyProeprtyInitializer(initializer,
+        expr => codeUtils.getObjectLiteralExpression(expr));
+  }
+
+  private getStringLiteralValueWithComments(initializer: LegacyPropertyInitializer | undefined): DataWithComments<string> | undefined {
+    return this.convertLegacyProeprtyInitializer(initializer,
+        expr => codeUtils.getStringLiteralValue(expr));
+  }
+
+  private getBooleanLiteralValueWithComments(initializer: LegacyPropertyInitializer | undefined): DataWithComments<boolean> | undefined {
+    return this.convertLegacyProeprtyInitializer(initializer,
+        expr => codeUtils.getBooleanLiteralValue(expr));
+  }
+
+
+  private getArrayLiteralExpressionWithComments(initializer: LegacyPropertyInitializer | undefined): DataWithComments<ts.ArrayLiteralExpression> | undefined {
+    return this.convertLegacyProeprtyInitializer(initializer,
+        expr => codeUtils.getArrayLiteralExpression(expr));
+  }
+
+  private getLegacyPropertyInitializer(props: Map<String, ts.ObjectLiteralElementLike>, propName: string): LegacyPropertyInitializer | undefined {
+    const property = props.get(propName);
+    if (!property) {
+      return undefined;
+    }
+    const assignment = codeUtils.getPropertyAssignment(property);
+    if (!assignment) {
+      return undefined;
+    }
+    const comments: string[] = codeUtils.getLeadingComments(property)
+          .filter(c => !this.isEventComment(c));
+    return {
+      data: assignment.initializer,
+      leadingComments: comments,
+    };
+  }
+
+  private isEventComment(comment: string): boolean {
+    return comment.indexOf('@event') >= 0;
+  }
+
+  private getEventsComments(polymerComponentSource: string): string[] {
+    return CommentsParser.collectAllComments(polymerComponentSource)
+        .filter(c => this.isEventComment(c));
+  }
+
+  private getLegacyMethodDeclaration(props: Map<String, ts.ObjectLiteralElementLike>, propName: string): ts.MethodDeclaration | undefined {
+    const property = props.get(propName);
+    if (!property) {
+      return undefined;
+    }
+    return codeUtils.assertNodeKind(property, ts.SyntaxKind.MethodDeclaration) as ts.MethodDeclaration;
+  }
+
+}
+
+export type ParsedPolymerComponent = LegacyPolymerComponent;
+
+export interface LegacyPolymerComponent {
+  jsFile: string;
+  htmlFile: string;
+  parsedFile: ts.SourceFile;
+  polymerFuncCallExpr: ts.CallExpression;
+  componentSettings: LegacyPolymerComponentSettings;
+}
+
+export interface LegacyReservedDeclarations {
+  is?: DataWithComments<string>;
+  _legacyUndefinedCheck?: DataWithComments<boolean>;
+  properties?: DataWithComments<ts.ObjectLiteralExpression>;
+  behaviors?: DataWithComments<ts.ArrayLiteralExpression>,
+  observers? :DataWithComments<ts.ArrayLiteralExpression>,
+  listeners? :DataWithComments<ts.ObjectLiteralExpression>,
+  hostAttributes?: DataWithComments<ts.ObjectLiteralExpression>,
+  keyBindings?: DataWithComments<ts.ObjectLiteralExpression>,
+}
+
+export const LegacyLifecycleMethodsArray = <const>["beforeRegister", "registered", "created", "ready", "attached" , "detached", "attributeChanged"];
+export type LegacyLifecycleMethodName = typeof LegacyLifecycleMethodsArray[number];
+export type LegacyLifecycleMethods = Map<LegacyLifecycleMethodName, ts.MethodDeclaration>;
+export type OrdinaryMethods = Map<string, ts.MethodDeclaration>;
+export type OrdinaryShorthandProperties = Map<string, ts.ShorthandPropertyAssignment>;
+export type OrdinaryGetAccessors = Map<string, ts.GetAccessorDeclaration>;
+export type OrdinaryPropertyAssignments = Map<string, ts.PropertyAssignment>;
+export type ReservedName = LegacyLifecycleMethodName | keyof LegacyReservedDeclarations;
+export const RESERVED_NAMES: {[x in ReservedName]: boolean} = {
+  attached: true,
+  detached: true,
+  ready: true,
+  created: true,
+  beforeRegister: true,
+  registered: true,
+  attributeChanged: true,
+  is: true,
+  _legacyUndefinedCheck: true,
+  properties: true,
+  behaviors: true,
+  observers: true,
+  listeners: true,
+  hostAttributes: true,
+  keyBindings: true,
+};
+
+export interface LegacyPolymerComponentSettings {
+  reservedDeclarations: LegacyReservedDeclarations;
+  lifecycleMethods: LegacyLifecycleMethods,
+  ordinaryMethods: OrdinaryMethods,
+  ordinaryShorthandProperties: OrdinaryShorthandProperties,
+  ordinaryGetAccessors: OrdinaryGetAccessors,
+  ordinaryPropertyAssignments: OrdinaryPropertyAssignments,
+  eventsComments: string[];
+}
+
+export interface DataWithComments<T> {
+  data: T;
+  leadingComments: string[];
+}
+
+type LegacyPropertyInitializer = DataWithComments<ts.Expression>;
\ No newline at end of file
diff --git a/tools/polygerrit-updater/src/funcToClassConversion/polymerElementBuilder.ts b/tools/polygerrit-updater/src/funcToClassConversion/polymerElementBuilder.ts
new file mode 100644
index 0000000..d6e113c
--- /dev/null
+++ b/tools/polygerrit-updater/src/funcToClassConversion/polymerElementBuilder.ts
@@ -0,0 +1,142 @@
+// 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.
+
+import {DataWithComments, LegacyPolymerComponent, LegacyReservedDeclarations, OrdinaryGetAccessors, OrdinaryMethods, OrdinaryPropertyAssignments, OrdinaryShorthandProperties} from './polymerComponentParser';
+import * as ts from 'typescript';
+import * as codeUtils from '../utils/codeUtils';
+import {LifecycleMethod} from './lifecycleMethodsBuilder';
+import {PolymerClassBuilder} from '../utils/polymerClassBuilder';
+import {SyntaxKind} from 'typescript';
+
+export interface ClassBasedPolymerElement {
+  classDeclaration: ts.ClassDeclaration;
+  componentRegistration: ts.ExpressionStatement;
+  eventsComments: string[];
+  generatedComments: string[];
+}
+
+export class PolymerElementBuilder {
+  private readonly reservedDeclarations: LegacyReservedDeclarations;
+  private readonly classBuilder: PolymerClassBuilder;
+  private mixins: ts.ExpressionWithTypeArguments | null;
+
+  public constructor(private readonly legacyComponent: LegacyPolymerComponent, className: string) {
+    this.reservedDeclarations = legacyComponent.componentSettings.reservedDeclarations;
+    this.classBuilder = new PolymerClassBuilder(className);
+    this.mixins = null;
+  }
+
+  public addIsAccessor(tagName: string) {
+    this.classBuilder.addIsAccessor(this.createIsAccessor(tagName));
+  }
+
+  public addPolymerPropertiesAccessor(legacyProperties: DataWithComments<ts.ObjectLiteralExpression>) {
+    const returnStatement = ts.createReturn(legacyProperties.data);
+    const block = ts.createBlock([returnStatement]);
+    let propertiesAccessor = ts.createGetAccessor(undefined, [ts.createModifier(ts.SyntaxKind.StaticKeyword)], "properties", [], undefined, block);
+    if(legacyProperties.leadingComments.length > 0) {
+      propertiesAccessor = codeUtils.restoreLeadingComments(propertiesAccessor, legacyProperties.leadingComments);
+    }
+    this.classBuilder.addPolymerPropertiesAccessor(legacyProperties.data.pos, propertiesAccessor);
+  }
+
+  public addPolymerPropertiesObservers(legacyObservers: ts.ArrayLiteralExpression) {
+    const returnStatement = ts.createReturn(legacyObservers);
+    const block = ts.createBlock([returnStatement]);
+    const propertiesAccessor = ts.createGetAccessor(undefined, [ts.createModifier(ts.SyntaxKind.StaticKeyword)], "observers", [], undefined, block);
+
+    this.classBuilder.addPolymerObserversAccessor(legacyObservers.pos, propertiesAccessor);
+  }
+
+  public addKeyBindings(keyBindings: ts.ObjectLiteralExpression) {
+    //In Polymer 2 keyBindings must be a property with get accessor
+    const returnStatement = ts.createReturn(keyBindings);
+    const block = ts.createBlock([returnStatement]);
+    const keyBindingsAccessor = ts.createGetAccessor(undefined, [], "keyBindings", [], undefined, block);
+
+    this.classBuilder.addGetAccessor(keyBindings.pos, keyBindingsAccessor);
+  }
+  public addOrdinaryMethods(ordinaryMethods: OrdinaryMethods) {
+    for(const [name, method] of ordinaryMethods) {
+      this.classBuilder.addMethod(method.pos, method);
+    }
+  }
+
+  public addOrdinaryGetAccessors(ordinaryGetAccessors: OrdinaryGetAccessors) {
+    for(const [name, accessor] of ordinaryGetAccessors) {
+      this.classBuilder.addGetAccessor(accessor.pos, accessor);
+    }
+  }
+
+  public addOrdinaryShorthandProperties(ordinaryShorthandProperties: OrdinaryShorthandProperties) {
+    for (const [name, property] of ordinaryShorthandProperties) {
+      this.classBuilder.addClassFieldInitializer(property.name, property.name);
+    }
+  }
+
+  public addOrdinaryPropertyAssignments(ordinaryPropertyAssignments: OrdinaryPropertyAssignments) {
+    for (const [name, property] of ordinaryPropertyAssignments) {
+      const propertyName = codeUtils.assertNodeKind(property.name, ts.SyntaxKind.Identifier) as ts.Identifier;
+      this.classBuilder.addClassFieldInitializer(propertyName, property.initializer);
+    }
+  }
+
+  public addMixin(name: string, mixinArguments?: ts.Expression[]) {
+    let fullMixinArguments: ts.Expression[] = [];
+    if(mixinArguments) {
+      fullMixinArguments.push(...mixinArguments);
+    }
+    if(this.mixins) {
+      fullMixinArguments.push(this.mixins.expression);
+    }
+    if(fullMixinArguments.length > 0) {
+      this.mixins = ts.createExpressionWithTypeArguments([], ts.createCall(codeUtils.createNameExpression(name), [], fullMixinArguments.length > 0 ? fullMixinArguments : undefined));
+    }
+    else {
+      this.mixins = ts.createExpressionWithTypeArguments([], codeUtils.createNameExpression(name));
+    }
+  }
+
+  public addClassJSDocComments(lines: string[]) {
+    this.classBuilder.addClassJSDocComments(lines);
+  }
+
+  public build(): ClassBasedPolymerElement {
+    if(this.mixins) {
+      this.classBuilder.setBaseType(this.mixins);
+    }
+    const className = this.classBuilder.className;
+    const callExpression = ts.createCall(ts.createPropertyAccess(ts.createIdentifier("customElements"), "define"), undefined, [ts.createPropertyAccess(ts.createIdentifier(className), "is"), ts.createIdentifier(className)]);
+    const classBuilderResult = this.classBuilder.build();
+    return {
+      classDeclaration: classBuilderResult.classDeclaration,
+      generatedComments: classBuilderResult.generatedComments,
+      componentRegistration: ts.createExpressionStatement(callExpression),
+      eventsComments: this.legacyComponent.componentSettings.eventsComments,
+    };
+  }
+
+  private createIsAccessor(tagName: string): ts.GetAccessorDeclaration {
+    const returnStatement = ts.createReturn(ts.createStringLiteral(tagName));
+    const block = ts.createBlock([returnStatement]);
+    const accessor = ts.createGetAccessor([], [ts.createModifier(ts.SyntaxKind.StaticKeyword)], "is", [], undefined, block);
+    return codeUtils.addReplacableCommentAfterNode(accessor, "eventsComments");
+  }
+
+  public addLifecycleMethods(newLifecycleMethods: LifecycleMethod[]) {
+    for(const lifecycleMethod of newLifecycleMethods) {
+      this.classBuilder.addLifecycleMethod(lifecycleMethod.name, lifecycleMethod.originalPos, lifecycleMethod.method);
+    }
+  }
+}
diff --git a/tools/polygerrit-updater/src/funcToClassConversion/updatedFileWriter.ts b/tools/polygerrit-updater/src/funcToClassConversion/updatedFileWriter.ts
new file mode 100644
index 0000000..a147f50
--- /dev/null
+++ b/tools/polygerrit-updater/src/funcToClassConversion/updatedFileWriter.ts
@@ -0,0 +1,248 @@
+// 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.
+
+import {LegacyPolymerComponent} from './polymerComponentParser';
+import * as ts from 'typescript';
+import * as codeUtils from '../utils/codeUtils';
+import * as path from "path";
+import * as fs from "fs";
+import {LegacyPolymerFuncReplaceResult} from './legacyPolymerFuncReplacer';
+import {CommentsParser} from '../utils/commentsParser';
+
+export interface UpdatedFileWriterParameters {
+  out: string;
+  inplace: boolean;
+  writeOutput: boolean;
+  rootDir: string;
+}
+
+interface Replacement {
+  start: number;
+  length: number;
+  newText: string;
+}
+
+const elementRegistrationRegex = /^(\s*)customElements.define\((\w+).is, \w+\);$/m;
+const maxLineLength = 80;
+
+export class UpdatedFileWriter {
+  public constructor(private readonly component: LegacyPolymerComponent, private readonly params: UpdatedFileWriterParameters) {
+  }
+
+  public write(replaceResult: LegacyPolymerFuncReplaceResult, eventsComments: string[], generatedComments: string[]) {
+    const options: ts.PrinterOptions = {
+      removeComments: false,
+      newLine: ts.NewLineKind.LineFeed,
+    };
+    const printer = ts.createPrinter(options);
+    let newContent = codeUtils.applyNewLines(printer.printFile(replaceResult.file));
+    //ts printer doesn't keep original formatting of the file (spacing, new lines, comments, etc...).
+    //The following code tries restore original formatting
+
+    const existingComments = this.collectAllComments(newContent, []);
+
+    newContent = this.restoreEventsComments(newContent, eventsComments, existingComments);
+    newContent = this.restoreLeadingComments(newContent, replaceResult.leadingComments);
+    newContent = this.restoreFormating(printer, newContent);
+    newContent = this.splitLongLines(newContent);
+    newContent = this.addCommentsWarnings(newContent, generatedComments);
+
+    if (this.params.writeOutput) {
+      const outDir = this.params.inplace ? this.params.rootDir : this.params.out;
+      const fullOutPath = path.resolve(outDir, this.component.jsFile);
+      const fullOutDir = path.dirname(fullOutPath);
+      if (!fs.existsSync(fullOutDir)) {
+        fs.mkdirSync(fullOutDir, {
+          recursive: true,
+          mode: fs.lstatSync(this.params.rootDir).mode
+        });
+      }
+      fs.writeFileSync(fullOutPath, newContent);
+    }
+  }
+
+  private restoreEventsComments(content: string, eventsComments: string[], existingComments: Map<string, number>): string {
+    //In some cases Typescript compiler keep existing comments. These comments
+    // must not be restored here
+    eventsComments = eventsComments.filter(c => !existingComments.has(this.getNormalizedComment(c)));
+    return codeUtils.replaceComment(content, "eventsComments", "\n" + eventsComments.join("\n\n") + "\n");
+  }
+
+  private restoreLeadingComments(content: string, leadingComments: string[]): string {
+    return leadingComments.reduce(
+        (newContent, comment, commentIndex) =>
+            codeUtils.replaceComment(newContent, String(commentIndex), comment),
+        content);
+  }
+
+  private restoreFormating(printer: ts.Printer, newContent: string): string {
+    const originalFile = this.component.parsedFile;
+    const newFile = ts.createSourceFile(originalFile.fileName, newContent, originalFile.languageVersion, true, ts.ScriptKind.JS);
+    const textMap = new Map<ts.SyntaxKind, Map<string, Set<string>>>();
+    const comments = new Set<string>();
+    this.collectAllStrings(printer, originalFile, textMap);
+
+    const replacements: Replacement[] = [];
+    this.collectReplacements(printer, newFile, textMap, replacements);
+    replacements.sort((a, b) => b.start - a.start);
+    let result = newFile.getFullText();
+    let prevReplacement: Replacement | null = null;
+    for (const replacement of replacements) {
+      if (prevReplacement) {
+        if (replacement.start + replacement.length > prevReplacement.start) {
+          throw new Error('Internal error! Replacements must not intersect');
+        }
+      }
+      result = result.substring(0, replacement.start) + replacement.newText + result.substring(replacement.start + replacement.length);
+      prevReplacement = replacement;
+    }
+    return result;
+  }
+
+  private splitLongLines(content: string): string {
+    content = content.replace(elementRegistrationRegex, (match, indent, className) => {
+      if (match.length > maxLineLength) {
+        return `${indent}customElements.define(${className}.is,\n` +
+            `${indent}  ${className});`;
+      }
+      else {
+        return match;
+      }
+    });
+
+    return content
+        .replace(
+            "Polymer.LegacyDataMixin(Polymer.GestureEventListeners(Polymer.LegacyElementMixin(Polymer.Element)))",
+            "Polymer.LegacyDataMixin(\nPolymer.GestureEventListeners(\nPolymer.LegacyElementMixin(\nPolymer.Element)))")
+        .replace(
+            "Polymer.GestureEventListeners(Polymer.LegacyElementMixin(Polymer.Element))",
+            "Polymer.GestureEventListeners(\nPolymer.LegacyElementMixin(\nPolymer.Element))");
+
+  }
+
+  private addCommentsWarnings(newContent: string, generatedComments: string[]): string {
+    const expectedComments = this.collectAllComments(this.component.parsedFile.getFullText(), generatedComments);
+    const newComments = this.collectAllComments(newContent, []);
+    const commentsWarnings = [];
+    for (const [text, count] of expectedComments) {
+      const newCount = newComments.get(text);
+      if (!newCount) {
+        commentsWarnings.push(`Comment '${text}' is missing in the new content.`);
+      }
+      else if (newCount != count) {
+        commentsWarnings.push(`Comment '${text}' appears ${newCount} times in the new file and ${count} times in the old file.`);
+      }
+    }
+
+    for (const [text, newCount] of newComments) {
+      if (!expectedComments.has(text)) {
+        commentsWarnings.push(`Comment '${text}' appears only in the new content`);
+      }
+    }
+    if (commentsWarnings.length === 0) {
+      return newContent;
+    }
+    let commentsProblemStr = "";
+    if (commentsWarnings.length > 0) {
+      commentsProblemStr = commentsWarnings.join("-----------------------------\n");
+      console.log(commentsProblemStr);
+    }
+
+    return "//This file has the following problems with comments:\n" + commentsProblemStr + "\n" + newContent;
+
+  }
+
+  private collectAllComments(content: string, additionalComments: string[]): Map<string, number> {
+    const comments = CommentsParser.collectAllComments(content);
+    comments.push(...additionalComments);
+    const result = new Map<string, number>();
+    for (const comment of comments) {
+      let normalizedComment = this.getNormalizedComment(comment);
+      const count = result.get(normalizedComment);
+      if (count) {
+        result.set(normalizedComment, count + 1);
+      } else {
+        result.set(normalizedComment, 1);
+      }
+    }
+    return result;
+  }
+
+  private getNormalizedComment(comment: string): string {
+    if(comment.startsWith('/**')) {
+      comment = comment.replace(/^\s+\*/gm, "*");
+    }
+    return comment;
+  }
+
+  private collectAllStrings(printer: ts.Printer, node: ts.Node, map: Map<ts.SyntaxKind, Map<string, Set<string>>>) {
+    const formattedText = printer.printNode(ts.EmitHint.Unspecified, node, node.getSourceFile())
+    const originalText = node.getFullText();
+    this.addIfNotExists(map, node.kind, formattedText, originalText);
+    ts.forEachChild(node, child => this.collectAllStrings(printer, child, map));
+  }
+
+  private collectReplacements(printer: ts.Printer, node: ts.Node, map: Map<ts.SyntaxKind, Map<string, Set<string>>>, replacements: Replacement[]) {
+    if(node.kind === ts.SyntaxKind.ThisKeyword || node.kind === ts.SyntaxKind.Identifier || node.kind === ts.SyntaxKind.StringLiteral || node.kind === ts.SyntaxKind.NumericLiteral) {
+      return;
+    }
+    const replacement = this.getReplacement(printer, node, map);
+    if(replacement) {
+      replacements.push(replacement);
+      return;
+    }
+    ts.forEachChild(node, child => this.collectReplacements(printer, child, map, replacements));
+  }
+
+  private addIfNotExists(map: Map<ts.SyntaxKind, Map<string, Set<string>>>, kind: ts.SyntaxKind, formattedText: string, originalText: string) {
+    let mapForKind = map.get(kind);
+    if(!mapForKind) {
+      mapForKind = new Map();
+      map.set(kind, mapForKind);
+    }
+
+    let existingOriginalText = mapForKind.get(formattedText);
+    if(!existingOriginalText) {
+      existingOriginalText = new Set<string>();
+      mapForKind.set(formattedText, existingOriginalText);
+      //throw new Error(`Different formatting of the same string exists. Kind: ${ts.SyntaxKind[kind]}.\nFormatting 1:\n${originalText}\nFormatting2:\n${existingOriginalText}\n `);
+    }
+    existingOriginalText.add(originalText);
+  }
+
+  private getReplacement(printer: ts.Printer, node: ts.Node, map: Map<ts.SyntaxKind, Map<string, Set<string>>>): Replacement | undefined {
+    const replacementsForKind = map.get(node.kind);
+    if(!replacementsForKind) {
+      return;
+    }
+    // Use printer instead of getFullText to "isolate" node content.
+    // node.getFullText returns text with indents from the original file.
+    const newText = printer.printNode(ts.EmitHint.Unspecified, node, node.getSourceFile());
+    const originalSet = replacementsForKind.get(newText);
+    if(!originalSet || originalSet.size === 0) {
+      return;
+    }
+    if(originalSet.size >= 2) {
+      console.log(`Multiple replacements possible. Formatting of some lines can be changed`);
+    }
+    const replacementText: string = originalSet.values().next().value;
+    const nodeText = node.getFullText();
+    return {
+      start: node.pos,
+      length: nodeText.length,//Do not use newText here!
+      newText: replacementText,
+    }
+  }
+
+}
\ No newline at end of file
diff --git a/tools/polygerrit-updater/src/index.ts b/tools/polygerrit-updater/src/index.ts
new file mode 100644
index 0000000..1b7c315
--- /dev/null
+++ b/tools/polygerrit-updater/src/index.ts
@@ -0,0 +1,168 @@
+// 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.
+
+import * as fs from "fs";
+import * as path from "path";
+import {LegacyPolymerComponent, LegacyPolymerComponentParser} from './funcToClassConversion/polymerComponentParser';
+import {ClassBasedPolymerElement} from './funcToClassConversion/polymerElementBuilder';
+import {PolymerFuncToClassBasedConverter} from './funcToClassConversion/funcToClassBasedElementConverter';
+import {LegacyPolymerFuncReplacer} from './funcToClassConversion/legacyPolymerFuncReplacer';
+import {UpdatedFileWriter} from './funcToClassConversion/updatedFileWriter';
+import {CommandLineParser} from './utils/commandLineParser';
+
+interface UpdaterParameters {
+  htmlFiles: Set<string>;
+  jsFiles: Set<string>;
+  out: string;
+  inplace: boolean;
+  writeOutput: boolean;
+  rootDir: string;
+}
+
+interface InputFilesFilter {
+  includeDir(path: string): boolean;
+  includeFile(path: string): boolean;
+}
+
+function addFile(filePath: string, params: UpdaterParameters, filter: InputFilesFilter) {
+  const parsedPath = path.parse(filePath);
+  const ext = parsedPath.ext.toLowerCase();
+  const relativePath = path.relative(params.rootDir, filePath);
+  if(!filter.includeFile(relativePath)) return;
+  if(relativePath.startsWith("../")) {
+    throw new Error(`${filePath} is not in rootDir ${params.rootDir}`);
+  }
+  if(ext === ".html") {
+    params.htmlFiles.add(relativePath);
+  } if(ext === ".js") {
+    params.jsFiles.add(relativePath);
+  }
+}
+
+function addDirectory(dirPath: string, params: UpdaterParameters, recursive: boolean, filter: InputFilesFilter): void {
+  const entries = fs.readdirSync(dirPath, {withFileTypes: true});
+  for(const entry of entries) {
+    const dirEnt = entry as fs.Dirent;
+    const fullPath = path.join(dirPath, dirEnt.name);
+    const relativePath = path.relative(params.rootDir, fullPath);
+    if(dirEnt.isDirectory()) {
+      if (!filter.includeDir(relativePath)) continue;
+      if(recursive) {
+        addDirectory(fullPath, params, recursive, filter);
+      }
+    }
+    else if(dirEnt.isFile()) {
+      addFile(fullPath, params, filter);
+    } else {
+      throw Error(`Unsupported dir entry '${entry.name}' in '${fullPath}'`);
+    }
+  }
+}
+
+async function updateLegacyComponent(component: LegacyPolymerComponent, params: UpdaterParameters) {
+  const classBasedElement: ClassBasedPolymerElement = PolymerFuncToClassBasedConverter.convert(component);
+
+  const replacer = new LegacyPolymerFuncReplacer(component);
+  const replaceResult = replacer.replace(classBasedElement);
+  try {
+    const writer = new UpdatedFileWriter(component, params);
+    writer.write(replaceResult, classBasedElement.eventsComments, classBasedElement.generatedComments);
+  }
+  finally {
+    replaceResult.dispose();
+  }
+}
+
+async function main() {
+  const params: UpdaterParameters = await getParams();
+  if(params.jsFiles.size === 0) {
+    console.log("No files found");
+    return;
+  }
+  const legacyPolymerComponentParser = new LegacyPolymerComponentParser(params.rootDir, params.htmlFiles)
+  for(const jsFile of params.jsFiles) {
+    console.log(`Processing ${jsFile}`);
+    const legacyComponent = await legacyPolymerComponentParser.parse(jsFile);
+    if(legacyComponent) {
+      await updateLegacyComponent(legacyComponent, params);
+      continue;
+    }
+  }
+}
+
+interface CommandLineParameters {
+  src: string[];
+  recursive: boolean;
+  excludes: string[];
+  out: string;
+  inplace: boolean;
+  noOutput: boolean;
+  rootDir: string;
+}
+
+async function getParams(): Promise<UpdaterParameters> {
+  const parser = new CommandLineParser({
+    src: CommandLineParser.createStringArrayOption("src", ".js file or folder to process", []),
+    recursive: CommandLineParser.createBooleanOption("r", "process folder recursive", false),
+    excludes: CommandLineParser.createStringArrayOption("exclude", "List of file prefixes to exclude. If relative file path starts with one of the prefixes, it will be excluded", []),
+    out: CommandLineParser.createStringOption("out", "Output folder.", null),
+    rootDir: CommandLineParser.createStringOption("root", "Root directory for src files", "/"),
+    inplace: CommandLineParser.createBooleanOption("i", "Update files inplace", false),
+    noOutput: CommandLineParser.createBooleanOption("noout", "Do everything, but do not write anything to files", false),
+  });
+  const commandLineParams: CommandLineParameters = parser.parse(process.argv) as CommandLineParameters;
+
+  const params: UpdaterParameters = {
+    htmlFiles: new Set(),
+    jsFiles: new Set(),
+    writeOutput: !commandLineParams.noOutput,
+    inplace: commandLineParams.inplace,
+    out: commandLineParams.out,
+    rootDir: path.resolve(commandLineParams.rootDir)
+  };
+
+  if(params.writeOutput && !params.inplace && !params.out) {
+    throw new Error("You should specify output directory (--out directory_name)");
+  }
+
+  const filter = new ExcludeFilesFilter(commandLineParams.excludes);
+  for(const srcPath of commandLineParams.src) {
+    const resolvedPath = path.resolve(params.rootDir, srcPath);
+    if(fs.lstatSync(resolvedPath).isFile()) {
+      addFile(resolvedPath, params, filter);
+    } else {
+      addDirectory(resolvedPath, params, commandLineParams.recursive, filter);
+    }
+  }
+  return params;
+}
+
+class ExcludeFilesFilter implements InputFilesFilter {
+  public constructor(private readonly excludes: string[]) {
+  }
+  includeDir(path: string): boolean {
+    return this.excludes.every(exclude => !path.startsWith(exclude));
+  }
+
+  includeFile(path: string): boolean {
+    return this.excludes.every(exclude => !path.startsWith(exclude));
+  }
+}
+
+main().then(() => {
+  process.exit(0);
+}).catch(e => {
+  console.error(e);
+  process.exit(1);
+});
diff --git a/tools/polygerrit-updater/src/utils/codeUtils.ts b/tools/polygerrit-updater/src/utils/codeUtils.ts
new file mode 100644
index 0000000..53a7f0d
--- /dev/null
+++ b/tools/polygerrit-updater/src/utils/codeUtils.ts
@@ -0,0 +1,183 @@
+// 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.
+
+import * as ts from 'typescript';
+import {SyntaxKind} from 'typescript';
+import {Node} from 'typescript';
+
+export function assertNodeKind<T extends U, U extends ts.Node>(node: U, expectedKind: ts.SyntaxKind): T {
+  if (node.kind !== expectedKind) {
+    throw new Error(`Invlid node kind. Expected: ${ts.SyntaxKind[expectedKind]}, actual: ${ts.SyntaxKind[node.kind]}`);
+  }
+  return node as T;
+}
+
+export function assertNodeKindOrUndefined<T extends U, U extends ts.Node>(node: U | undefined, expectedKind: ts.SyntaxKind): T | undefined {
+  if (!node) {
+    return undefined;
+  }
+  return assertNodeKind<T, U>(node, expectedKind);
+}
+
+export function getPropertyAssignment(expression?: ts.ObjectLiteralElementLike): ts.PropertyAssignment | undefined {
+  return assertNodeKindOrUndefined(expression, ts.SyntaxKind.PropertyAssignment);
+}
+
+export function getStringLiteralValue(expression: ts.Expression): string {
+  const literal: ts.StringLiteral = assertNodeKind(expression, ts.SyntaxKind.StringLiteral);
+  return literal.text;
+}
+
+export function getBooleanLiteralValue(expression: ts.Expression): boolean {
+  if (expression.kind === ts.SyntaxKind.TrueKeyword) {
+    return true;
+  }
+  if (expression.kind === ts.SyntaxKind.FalseKeyword) {
+    return false;
+  }
+  throw new Error(`Invalid expression kind - ${expression.kind}`);
+}
+
+export function getObjectLiteralExpression(expression: ts.Expression): ts.ObjectLiteralExpression {
+  return assertNodeKind(expression, ts.SyntaxKind.ObjectLiteralExpression);
+}
+
+export function getArrayLiteralExpression(expression: ts.Expression): ts.ArrayLiteralExpression {
+  return assertNodeKind(expression, ts.SyntaxKind.ArrayLiteralExpression);
+}
+
+export function replaceNode(file: ts.SourceFile, originalNode: ts.Node, newNode: ts.Node): ts.TransformationResult<ts.SourceFile> {
+  const nodeReplacerTransformer: ts.TransformerFactory<ts.SourceFile> = (context: ts.TransformationContext) => {
+    const visitor: ts.Visitor = (node) => {
+      if(node === originalNode) {
+        return newNode;
+      }
+      return ts.visitEachChild(node, visitor, context);
+    };
+
+
+    return source => ts.visitNode(source, visitor);
+  };
+  return ts.transform(file, [nodeReplacerTransformer]);
+}
+
+export type NameExpression = ts.Identifier | ts.ThisExpression | ts.PropertyAccessExpression;
+export function createNameExpression(fullPath: string): NameExpression {
+  const parts = fullPath.split(".");
+  let result: NameExpression = parts[0] === "this" ? ts.createThis() : ts.createIdentifier(parts[0]);
+  for(let i = 1; i < parts.length; i++) {
+    result = ts.createPropertyAccess(result, parts[i]);
+  }
+  return result;
+}
+
+const generatedCommentNewLineAfterText = "-Generated code - new line after - 9cb292bc-5d88-4c5e-88f4-49535c93beb9 -";
+const generatedCommentNewLineBeforeText = "-Generated code - new line-before - 9cb292bc-5d88-4c5e-88f4-49535c93beb9 -";
+const generatedCommentNewLineAfterRegExp = new RegExp("//" + generatedCommentNewLineAfterText, 'g');
+const generatedCommentNewLineBeforeRegExp = new RegExp("//" + generatedCommentNewLineBeforeText + "\n", 'g');
+const replacableCommentText = "- Replacepoint - 9cb292bc-5d88-4c5e-88f4-49535c93beb9 -";
+
+export function addNewLineAfterNode<T extends ts.Node>(node: T): T {
+  const comment = ts.getSyntheticTrailingComments(node);
+  if(comment && comment.some(c => c.text === generatedCommentNewLineAfterText)) {
+    return node;
+  }
+  return ts.addSyntheticTrailingComment(node, ts.SyntaxKind.SingleLineCommentTrivia, generatedCommentNewLineAfterText, true);
+}
+
+export function addNewLineBeforeNode<T extends ts.Node>(node: T): T {
+  const comment = ts.getSyntheticLeadingComments(node);
+  if(comment && comment.some(c => c.text === generatedCommentNewLineBeforeText)) {
+    return node;
+  }
+  return ts.addSyntheticLeadingComment(node, ts.SyntaxKind.SingleLineCommentTrivia, generatedCommentNewLineBeforeText, true);
+}
+
+
+export function applyNewLines(text: string): string {
+  return text.replace(generatedCommentNewLineAfterRegExp, "").replace(generatedCommentNewLineBeforeRegExp, "");
+
+}
+export function addReplacableCommentAfterNode<T extends ts.Node>(node: T, name: string): T {
+  return ts.addSyntheticTrailingComment(node, ts.SyntaxKind.SingleLineCommentTrivia, replacableCommentText + name, true);
+}
+
+export function addReplacableCommentBeforeNode<T extends ts.Node>(node: T, name: string): T {
+  return ts.addSyntheticLeadingComment(node, ts.SyntaxKind.SingleLineCommentTrivia, replacableCommentText + name, true);
+}
+
+export function replaceComment(text: string, commentName: string, newContent: string): string {
+  return text.replace("//" + replacableCommentText + commentName, newContent);
+}
+
+export function createMethod(name: string, methodDecl: ts.MethodDeclaration | undefined, codeAtStart: ts.Statement[], codeAtEnd: ts.Statement[], callSuperMethod: boolean): ts.MethodDeclaration | undefined {
+  if(!methodDecl && (codeAtEnd.length > 0 || codeAtEnd.length > 0)) {
+    methodDecl = ts.createMethod([], [], undefined, name, undefined, [], [],undefined, ts.createBlock([]));
+  }
+  if(!methodDecl) {
+    return;
+  }
+  if (!methodDecl.body) {
+    throw new Error("Method must have a body");
+  }
+  if(methodDecl.parameters.length > 0) {
+    throw new Error("Methods with parameters are not supported");
+  }
+  let newStatements = [...codeAtStart];
+  if(callSuperMethod) {
+    const superCall: ts.CallExpression = ts.createCall(ts.createPropertyAccess(ts.createSuper(), assertNodeKind(methodDecl.name, ts.SyntaxKind.Identifier) as ts.Identifier), [], []);
+    const superCallExpression = ts.createExpressionStatement(superCall);
+    newStatements.push(superCallExpression);
+  }
+  newStatements.push(...codeAtEnd);
+  const newBody = ts.getMutableClone(methodDecl.body);
+
+  newStatements = newStatements.map(m => addNewLineAfterNode(m));
+  newStatements.splice(codeAtStart.length + 1, 0, ...newBody.statements);
+
+  newBody.statements = ts.createNodeArray(newStatements);
+
+  const newMethod = ts.getMutableClone(methodDecl);
+  newMethod.body = newBody;
+
+  return newMethod;
+}
+
+export function restoreLeadingComments<T extends Node>(node: T, originalComments: string[]): T {
+  if(originalComments.length === 0) {
+    return node;
+  }
+  for(const comment of originalComments) {
+    if(comment.startsWith("//")) {
+      node = ts.addSyntheticLeadingComment(node, SyntaxKind.SingleLineCommentTrivia, comment.substr(2), true);
+    } else if(comment.startsWith("/*")) {
+      if(!comment.endsWith("*/")) {
+        throw new Error(`Not support comment: ${comment}`);
+      }
+      node = ts.addSyntheticLeadingComment(node, SyntaxKind.MultiLineCommentTrivia, comment.substr(2, comment.length - 4), true);
+    } else {
+      throw new Error(`Not supported comment: ${comment}`);
+    }
+  }
+  return node;
+}
+
+export function getLeadingComments(node: ts.Node): string[] {
+  const nodeText = node.getFullText();
+  const commentRanges = ts.getLeadingCommentRanges(nodeText, 0);
+  if(!commentRanges) {
+    return [];
+  }
+  return commentRanges.map(range => nodeText.substring(range.pos, range.end))
+}
\ No newline at end of file
diff --git a/tools/polygerrit-updater/src/utils/commandLineParser.ts b/tools/polygerrit-updater/src/utils/commandLineParser.ts
new file mode 100644
index 0000000..658b7ff
--- /dev/null
+++ b/tools/polygerrit-updater/src/utils/commandLineParser.ts
@@ -0,0 +1,134 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed un  der 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.
+
+export class CommandLineParser {
+  public static createStringArrayOption(optionName: string, help: string, defaultValue: string[]): CommandLineArgument {
+    return new StringArrayOption(optionName, help, defaultValue);
+  }
+  public static createBooleanOption(optionName: string, help: string, defaultValue: boolean): CommandLineArgument {
+    return new BooleanOption(optionName, help, defaultValue);
+  }
+  public static createStringOption(optionName: string, help: string, defaultValue: string | null): CommandLineArgument {
+    return new StringOption(optionName, help, defaultValue);
+  }
+
+  public constructor(private readonly argumentTypes: {[name: string]: CommandLineArgument}) {
+  }
+  public parse(argv: string[]): object {
+    const result = Object.assign({});
+    let index = 2; //argv[0] - node interpreter, argv[1] - index.js
+    for(const argumentField in this.argumentTypes) {
+      result[argumentField] = this.argumentTypes[argumentField].getDefaultValue();
+    }
+    while(index < argv.length) {
+      let knownArgument = false;
+      for(const argumentField in this.argumentTypes) {
+        const argumentType = this.argumentTypes[argumentField];
+        const argumentValue = argumentType.tryRead(argv, index);
+        if(argumentValue) {
+          knownArgument = true;
+          index += argumentValue.consumed;
+          result[argumentField] = argumentValue.value;
+          break;
+        }
+      }
+      if(!knownArgument) {
+        throw new Error(`Unknown argument ${argv[index]}`);
+      }
+    }
+    return result;
+  }
+}
+
+interface CommandLineArgumentReadResult {
+  consumed: number;
+  value: any;
+}
+
+export interface CommandLineArgument {
+  getDefaultValue(): any;
+  tryRead(argv: string[], startIndex: number): CommandLineArgumentReadResult | null;
+}
+
+abstract class CommandLineOption implements CommandLineArgument {
+  protected constructor(protected readonly optionName: string, protected readonly help: string, private readonly defaultValue: any) {
+  }
+  public tryRead(argv: string[], startIndex: number): CommandLineArgumentReadResult | null  {
+    if(argv[startIndex] !== "--" + this.optionName) {
+      return null;
+    }
+    const readArgumentsResult = this.readArguments(argv, startIndex + 1);
+    if(!readArgumentsResult) {
+      return null;
+    }
+    readArgumentsResult.consumed++; // Add option name
+    return readArgumentsResult;
+  }
+  public getDefaultValue(): any {
+    return this.defaultValue;
+  }
+
+  protected abstract readArguments(argv: string[], startIndex: number) : CommandLineArgumentReadResult | null;
+}
+
+class StringArrayOption extends CommandLineOption {
+  public constructor(optionName: string, help: string, defaultValue: string[]) {
+    super(optionName, help, defaultValue);
+  }
+
+  protected readArguments(argv: string[], startIndex: number): CommandLineArgumentReadResult {
+    const result = [];
+    let index = startIndex;
+    while(index < argv.length) {
+      if(argv[index].startsWith("--")) {
+        break;
+      }
+      result.push(argv[index]);
+      index++;
+    }
+    return {
+      consumed: index - startIndex,
+      value: result
+    }
+  }
+}
+
+class BooleanOption extends CommandLineOption {
+  public constructor(optionName: string, help: string, defaultValue: boolean) {
+    super(optionName, help, defaultValue);
+  }
+
+  protected readArguments(argv: string[], startIndex: number): CommandLineArgumentReadResult {
+    return {
+      consumed: 0,
+      value: true
+    }
+  }
+}
+
+class StringOption extends CommandLineOption {
+  public constructor(optionName: string, help: string, defaultValue: string | null) {
+    super(optionName, help, defaultValue);
+  }
+
+  protected readArguments(argv: string[], startIndex: number): CommandLineArgumentReadResult | null {
+    if(startIndex >= argv.length) {
+      return null;
+    }
+    return {
+      consumed: 1,
+      value: argv[startIndex]
+    }
+  }
+}
diff --git a/tools/polygerrit-updater/src/utils/commentsParser.ts b/tools/polygerrit-updater/src/utils/commentsParser.ts
new file mode 100644
index 0000000..b849829
--- /dev/null
+++ b/tools/polygerrit-updater/src/utils/commentsParser.ts
@@ -0,0 +1,79 @@
+// 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.
+
+enum CommentScannerState {
+  Text,
+  SingleLineComment,
+  MultLineComment
+}
+export class CommentsParser {
+  public static collectAllComments(text: string): string[] {
+    const result: string[] = [];
+    let state = CommentScannerState.Text;
+    let pos = 0;
+    function readSingleLineComment() {
+      const startPos = pos;
+      while(pos < text.length && text[pos] !== '\n') {
+        pos++;
+      }
+      return text.substring(startPos, pos);
+    }
+    function readMultiLineComment() {
+      const startPos = pos;
+      while(pos < text.length) {
+        if(pos < text.length - 1 && text[pos] === '*' && text[pos + 1] === '/') {
+          pos += 2;
+          break;
+        }
+        pos++;
+      }
+      return text.substring(startPos, pos);
+    }
+
+    function skipString(lastChar: string) {
+      pos++;
+      while(pos < text.length) {
+        if(text[pos] === lastChar) {
+          pos++;
+          return;
+        } else if(text[pos] === '\\') {
+          pos+=2;
+          continue;
+        }
+        pos++;
+      }
+    }
+
+
+    while(pos < text.length - 1) {
+      if(text[pos] === '/' && text[pos + 1] === '/') {
+        result.push(readSingleLineComment());
+      } else if(text[pos] === '/' && text[pos + 1] === '*') {
+        result.push(readMultiLineComment());
+      } else if(text[pos] === "'") {
+        skipString("'");
+      } else if(text[pos] === '"') {
+        skipString('"');
+      } else if(text[pos] === '`') {
+        skipString('`');
+      } else if(text[pos] == '/') {
+        skipString('/');
+      } {
+        pos++;
+      }
+
+    }
+    return result;
+  }
+}
diff --git a/tools/polygerrit-updater/src/utils/polymerClassBuilder.ts b/tools/polygerrit-updater/src/utils/polymerClassBuilder.ts
new file mode 100644
index 0000000..b1a4320
--- /dev/null
+++ b/tools/polygerrit-updater/src/utils/polymerClassBuilder.ts
@@ -0,0 +1,270 @@
+// 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.
+
+import * as ts from 'typescript';
+import * as codeUtils from './codeUtils';
+import {LegacyLifecycleMethodName, LegacyLifecycleMethodsArray} from '../funcToClassConversion/polymerComponentParser';
+import {SyntaxKind} from 'typescript';
+
+enum PolymerClassMemberType {
+  IsAccessor,
+  Constructor,
+  PolymerPropertiesAccessor,
+  PolymerObserversAccessor,
+  Method,
+  ExistingLifecycleMethod,
+  NewLifecycleMethod,
+  GetAccessor,
+}
+
+type PolymerClassMember = PolymerClassIsAccessor | PolymerClassConstructor | PolymerClassExistingLifecycleMethod | PolymerClassNewLifecycleMethod | PolymerClassSimpleMember;
+
+interface PolymerClassExistingLifecycleMethod {
+  member: ts.MethodDeclaration;
+  memberType: PolymerClassMemberType.ExistingLifecycleMethod;
+  name: string;
+  lifecycleOrder: number;
+  originalPos: number;
+}
+
+interface PolymerClassNewLifecycleMethod {
+  member: ts.MethodDeclaration;
+  memberType: PolymerClassMemberType.NewLifecycleMethod;
+  name: string;
+  lifecycleOrder: number;
+  originalPos: -1
+}
+
+interface PolymerClassIsAccessor {
+  member: ts.GetAccessorDeclaration;
+  memberType: PolymerClassMemberType.IsAccessor;
+  originalPos: -1
+}
+
+interface PolymerClassConstructor {
+  member: ts.ConstructorDeclaration;
+  memberType: PolymerClassMemberType.Constructor;
+  originalPos: -1
+}
+
+interface PolymerClassSimpleMember {
+  memberType: PolymerClassMemberType.PolymerPropertiesAccessor | PolymerClassMemberType.PolymerObserversAccessor | PolymerClassMemberType.Method | PolymerClassMemberType.GetAccessor;
+  member: ts.ClassElement;
+  originalPos: number;
+}
+
+export interface PolymerClassBuilderResult {
+  classDeclaration: ts.ClassDeclaration;
+  generatedComments: string[];
+}
+
+export class PolymerClassBuilder {
+  private readonly members: PolymerClassMember[] = [];
+  public readonly constructorStatements: ts.Statement[] = [];
+  private baseType: ts.ExpressionWithTypeArguments | undefined;
+  private classJsDocComments: string[];
+
+  public constructor(public readonly className: string) {
+    this.classJsDocComments = [];
+  }
+
+  public addIsAccessor(accessor: ts.GetAccessorDeclaration) {
+    this.members.push({
+      member: accessor,
+      memberType: PolymerClassMemberType.IsAccessor,
+      originalPos: -1
+    });
+  }
+
+  public addPolymerPropertiesAccessor(originalPos: number, accessor: ts.GetAccessorDeclaration) {
+    this.members.push({
+      member: accessor,
+      memberType: PolymerClassMemberType.PolymerPropertiesAccessor,
+      originalPos: originalPos
+    });
+  }
+
+  public addPolymerObserversAccessor(originalPos: number, accessor: ts.GetAccessorDeclaration) {
+    this.members.push({
+      member: accessor,
+      memberType: PolymerClassMemberType.PolymerObserversAccessor,
+      originalPos: originalPos
+    });
+  }
+
+
+  public addClassFieldInitializer(name: string | ts.Identifier, initializer: ts.Expression) {
+    const assignment = ts.createAssignment(ts.createPropertyAccess(ts.createThis(), name), initializer);
+    this.constructorStatements.push(codeUtils.addNewLineAfterNode(ts.createExpressionStatement(assignment)));
+  }
+  public addMethod(originalPos: number, method: ts.MethodDeclaration) {
+    this.members.push({
+      member: method,
+      memberType: PolymerClassMemberType.Method,
+      originalPos: originalPos
+    });
+  }
+
+  public addGetAccessor(originalPos: number, accessor: ts.GetAccessorDeclaration) {
+    this.members.push({
+      member: accessor,
+      memberType: PolymerClassMemberType.GetAccessor,
+      originalPos: originalPos
+    });
+  }
+
+  public addLifecycleMethod(name: LegacyLifecycleMethodName, originalPos: number, method: ts.MethodDeclaration) {
+    const lifecycleOrder = LegacyLifecycleMethodsArray.indexOf(name);
+    if(lifecycleOrder < 0) {
+      throw new Error(`Invalid lifecycle name`);
+    }
+    if(originalPos >= 0) {
+      this.members.push({
+        member: method,
+        memberType: PolymerClassMemberType.ExistingLifecycleMethod,
+        originalPos: originalPos,
+        name: name,
+        lifecycleOrder: lifecycleOrder
+      })
+    } else {
+      this.members.push({
+        member: method,
+        memberType: PolymerClassMemberType.NewLifecycleMethod,
+        name: name,
+        lifecycleOrder: lifecycleOrder,
+        originalPos: -1
+      })
+    }
+  }
+
+  public setBaseType(type: ts.ExpressionWithTypeArguments) {
+    if(this.baseType) {
+      throw new Error("Class can have only one base type");
+    }
+    this.baseType = type;
+  }
+
+  public build(): PolymerClassBuilderResult {
+    let heritageClauses: ts.HeritageClause[] = [];
+    if (this.baseType) {
+      const extendClause = ts.createHeritageClause(ts.SyntaxKind.ExtendsKeyword, [this.baseType]);
+      heritageClauses.push(extendClause);
+    }
+    const finalMembers: PolymerClassMember[] = [];
+    const isAccessors = this.members.filter(member => member.memberType === PolymerClassMemberType.IsAccessor);
+    if(isAccessors.length !== 1) {
+      throw new Error("Class must have exactly one 'is'");
+    }
+    finalMembers.push(isAccessors[0]);
+    const constructorMember = this.createConstructor();
+    if(constructorMember) {
+      finalMembers.push(constructorMember);
+    }
+
+    const newLifecycleMethods: PolymerClassNewLifecycleMethod[] = [];
+    this.members.forEach(member => {
+      if(member.memberType === PolymerClassMemberType.NewLifecycleMethod) {
+        newLifecycleMethods.push(member);
+      }
+    });
+
+    const methodsWithKnownPosition = this.members.filter(member => member.originalPos >= 0);
+    methodsWithKnownPosition.sort((a, b) => a.originalPos - b.originalPos);
+
+    finalMembers.push(...methodsWithKnownPosition);
+
+
+    for(const newLifecycleMethod of newLifecycleMethods) {
+      //Number of methods is small - use brute force solution
+      let closestNextIndex = -1;
+      let closestNextOrderDiff: number = LegacyLifecycleMethodsArray.length;
+      let closestPrevIndex = -1;
+      let closestPrevOrderDiff: number = LegacyLifecycleMethodsArray.length;
+      for (let i = 0; i < finalMembers.length; i++) {
+        const member = finalMembers[i];
+        if (member.memberType !== PolymerClassMemberType.NewLifecycleMethod && member.memberType !== PolymerClassMemberType.ExistingLifecycleMethod) {
+          continue;
+        }
+        const orderDiff = member.lifecycleOrder - newLifecycleMethod.lifecycleOrder;
+        if (orderDiff > 0) {
+          if (orderDiff < closestNextOrderDiff) {
+            closestNextIndex = i;
+            closestNextOrderDiff = orderDiff;
+          }
+        } else if (orderDiff < 0) {
+          if (orderDiff < closestPrevOrderDiff) {
+            closestPrevIndex = i;
+            closestPrevOrderDiff = orderDiff;
+          }
+        }
+      }
+      let insertIndex;
+      if (closestNextIndex !== -1 || closestPrevIndex !== -1) {
+        insertIndex = closestNextOrderDiff < closestPrevOrderDiff ?
+            closestNextIndex : closestPrevIndex + 1;
+      } else {
+        insertIndex = Math.max(
+            finalMembers.findIndex(m => m.memberType === PolymerClassMemberType.Constructor),
+            finalMembers.findIndex(m => m.memberType === PolymerClassMemberType.IsAccessor),
+            finalMembers.findIndex(m => m.memberType === PolymerClassMemberType.PolymerPropertiesAccessor),
+            finalMembers.findIndex(m => m.memberType === PolymerClassMemberType.PolymerObserversAccessor),
+        );
+        if(insertIndex < 0) {
+          insertIndex = finalMembers.length;
+        } else {
+          insertIndex++;//Insert after
+        }
+      }
+      finalMembers.splice(insertIndex, 0, newLifecycleMethod);
+    }
+    //Asserts about finalMembers
+    const nonConstructorMembers = finalMembers.filter(m => m.memberType !== PolymerClassMemberType.Constructor);
+
+    if(nonConstructorMembers.length !== this.members.length) {
+      throw new Error(`Internal error! Some methods are missed`);
+    }
+    let classDeclaration = ts.createClassDeclaration(undefined, undefined, this.className, undefined, heritageClauses, finalMembers.map(m => m.member))
+    const generatedComments: string[] = [];
+    if(this.classJsDocComments.length > 0) {
+      const commentContent = '*\n' + this.classJsDocComments.map(line => `* ${line}`).join('\n') + '\n';
+      classDeclaration = ts.addSyntheticLeadingComment(classDeclaration, ts.SyntaxKind.MultiLineCommentTrivia, commentContent, true);
+      generatedComments.push(`/*${commentContent}*/`);
+    }
+    return {
+      classDeclaration,
+      generatedComments,
+    };
+
+  }
+
+  private createConstructor(): PolymerClassConstructor | null {
+    if(this.constructorStatements.length === 0) {
+      return null;
+    }
+    let superCall: ts.CallExpression = ts.createCall(ts.createSuper(), [], []);
+    const superCallExpression = ts.createExpressionStatement(superCall);
+    const statements = [superCallExpression, ...this.constructorStatements];
+    const constructorDeclaration = ts.createConstructor([], [], [], ts.createBlock(statements, true));
+
+    return {
+      memberType: PolymerClassMemberType.Constructor,
+      member: constructorDeclaration,
+      originalPos: -1
+    };
+  }
+
+  public addClassJSDocComments(lines: string[]) {
+    this.classJsDocComments.push(...lines);
+  }
+}
\ No newline at end of file
diff --git a/tools/polygerrit-updater/src/utils/unexpectedValue.ts b/tools/polygerrit-updater/src/utils/unexpectedValue.ts
new file mode 100644
index 0000000..690c283
--- /dev/null
+++ b/tools/polygerrit-updater/src/utils/unexpectedValue.ts
@@ -0,0 +1,17 @@
+// 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.
+
+export function unexpectedValue<T>(x: T): never {
+  throw new Error(`Unexpected value '${x}'`);
+}
diff --git a/tools/polygerrit-updater/tsconfig.json b/tools/polygerrit-updater/tsconfig.json
new file mode 100644
index 0000000..37ff1b2
--- /dev/null
+++ b/tools/polygerrit-updater/tsconfig.json
@@ -0,0 +1,67 @@
+{
+  "compilerOptions": {
+    /* Basic Options */
+    // "incremental": true,                   /* Enable incremental compilation */
+    "target": "es2015",                          /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */
+    "module": "commonjs",                     /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */
+    // "lib": [],                             /* Specify library files to be included in the compilation. */
+    // "allowJs": true,                       /* Allow javascript files to be compiled. */
+    // "checkJs": true,                       /* Report errors in .js files. */
+    // "jsx": "preserve",                     /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */
+    // "declaration": true,                   /* Generates corresponding '.d.ts' file. */
+    // "declarationMap": true,                /* Generates a sourcemap for each corresponding '.d.ts' file. */
+    "sourceMap": true,                     /* Generates corresponding '.map' file. */
+    // "outFile": "./",                       /* Concatenate and emit output to single file. */
+    "outDir": "./js",                        /* Redirect output structure to the directory. */
+    "rootDir": ".",                       /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
+    // "composite": true,                     /* Enable project compilation */
+    // "tsBuildInfoFile": "./",               /* Specify file to store incremental compilation information */
+    // "removeComments": true,                /* Do not emit comments to output. */
+    // "noEmit": true,                        /* Do not emit outputs. */
+    // "importHelpers": true,                 /* Import emit helpers from 'tslib'. */
+    // "downlevelIteration": true,            /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
+    // "isolatedModules": true,               /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */
+
+    /* Strict Type-Checking Options */
+    "strict": true,                           /* Enable all strict type-checking options. */
+    "noImplicitAny": true,                 /* Raise error on expressions and declarations with an implied 'any' type. */
+    // "strictNullChecks": true,              /* Enable strict null checks. */
+    // "strictFunctionTypes": true,           /* Enable strict checking of function types. */
+    // "strictBindCallApply": true,           /* Enable strict 'bind', 'call', and 'apply' methods on functions. */
+    // "strictPropertyInitialization": true,  /* Enable strict checking of property initialization in classes. */
+    // "noImplicitThis": true,                /* Raise error on 'this' expressions with an implied 'any' type. */
+    // "alwaysStrict": true,                  /* Parse in strict mode and emit "use strict" for each source file. */
+
+    /* Additional Checks */
+    // "noUnusedLocals": true,                /* Report errors on unused locals. */
+    // "noUnusedParameters": true,            /* Report errors on unused parameters. */
+    // "noImplicitReturns": true,             /* Report error when not all code paths in function return a value. */
+    // "noFallthroughCasesInSwitch": true,    /* Report errors for fallthrough cases in switch statement. */
+
+    /* Module Resolution Options */
+    "moduleResolution": "node",            /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
+    "baseUrl": "./",                       /* Base directory to resolve non-absolute module names. */
+    "paths": {
+      "*": [ "node_modules/*" ]
+    },                           /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
+    // "rootDirs": [],                        /* List of root folders whose combined content represents the structure of the project at runtime. */
+    // "typeRoots": [],                       /* List of folders to include type definitions from. */
+    // "types": [],                           /* Type declaration files to be included in compilation. */
+    "allowSyntheticDefaultImports": true,  /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
+    "esModuleInterop": true                   /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
+    // "preserveSymlinks": true,              /* Do not resolve the real path of symlinks. */
+    // "allowUmdGlobalAccess": true,          /* Allow accessing UMD globals from modules. */
+
+    /* Source Map Options */
+    // "sourceRoot": "",                      /* Specify the location where debugger should locate TypeScript files instead of source locations. */
+    // "mapRoot": "",                         /* Specify the location where debugger should locate map files instead of generated locations. */
+    // "inlineSourceMap": true,               /* Emit a single file with source maps instead of having a separate file. */
+    // "inlineSources": true,                 /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */
+
+    /* Experimental Options */
+    // "experimentalDecorators": true,        /* Enables experimental support for ES7 decorators. */
+    // "emitDecoratorMetadata": true,         /* Enables experimental support for emitting type metadata for decorators. */
+
+  },
+  "include": ["./src/**/*"]
+}