Merge "Fixing DiffViewContentDisplayed metric"
diff --git a/.bazelrc b/.bazelrc
index fef1fa3..bf3aa6c 100644
--- a/.bazelrc
+++ b/.bazelrc
@@ -1,4 +1,4 @@
-build --workspace_status_command=./tools/workspace-status.sh --strategy=Closure=worker
+build --workspace_status_command="python ./tools/workspace_status.py" --strategy=Closure=worker
 build --repository_cache=~/.gerritcodereview/bazel-cache/repository
 build --action_env=PATH
 build --disk_cache=~/.gerritcodereview/bazel-cache/cas
diff --git a/java/com/google/gerrit/server/change/FileContentUtil.java b/java/com/google/gerrit/server/change/FileContentUtil.java
index 5c7946c..49c1fe2 100644
--- a/java/com/google/gerrit/server/change/FileContentUtil.java
+++ b/java/com/google/gerrit/server/change/FileContentUtil.java
@@ -100,7 +100,7 @@
 
   public BinaryResult getContent(
       Repository repo, ProjectState project, ObjectId revstr, String path)
-      throws IOException, ResourceNotFoundException {
+      throws IOException, ResourceNotFoundException, BadRequestException {
     try (RevWalk rw = new RevWalk(repo)) {
       RevCommit commit = rw.parseCommit(revstr);
       try (TreeWalk tw = TreeWalk.forPath(rw.getObjectReader(), path, commit.getTree())) {
@@ -114,6 +114,10 @@
           return BinaryResult.create(id.name()).setContentType(X_GIT_GITLINK).base64();
         }
 
+        if (mode == org.eclipse.jgit.lib.FileMode.TREE) {
+          throw new BadRequestException("cannot retrieve content of directories");
+        }
+
         ObjectLoader obj = repo.open(id, OBJ_BLOB);
         byte[] raw;
         try {
diff --git a/java/com/google/gerrit/server/fixes/FixReplacementInterpreter.java b/java/com/google/gerrit/server/fixes/FixReplacementInterpreter.java
index a1682fe..9d6df7d 100644
--- a/java/com/google/gerrit/server/fixes/FixReplacementInterpreter.java
+++ b/java/com/google/gerrit/server/fixes/FixReplacementInterpreter.java
@@ -20,6 +20,7 @@
 import com.google.gerrit.common.RawInputUtil;
 import com.google.gerrit.entities.Comment;
 import com.google.gerrit.entities.FixReplacement;
+import com.google.gerrit.extensions.restapi.BadRequestException;
 import com.google.gerrit.extensions.restapi.BinaryResult;
 import com.google.gerrit.extensions.restapi.ResourceConflictException;
 import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
@@ -69,7 +70,8 @@
       ProjectState projectState,
       ObjectId patchSetCommitId,
       List<FixReplacement> fixReplacements)
-      throws ResourceNotFoundException, IOException, ResourceConflictException {
+      throws BadRequestException, ResourceNotFoundException, IOException,
+          ResourceConflictException {
     requireNonNull(fixReplacements, "Fix replacements must not be null");
 
     Map<String, List<FixReplacement>> fixReplacementsPerFilePath =
@@ -91,7 +93,8 @@
       ObjectId patchSetCommitId,
       String filePath,
       List<FixReplacement> fixReplacements)
-      throws ResourceNotFoundException, IOException, ResourceConflictException {
+      throws BadRequestException, ResourceNotFoundException, IOException,
+          ResourceConflictException {
     String fileContent = getFileContent(repository, projectState, patchSetCommitId, filePath);
     String newFileContent = getNewFileContent(fileContent, fixReplacements);
     return new ChangeFileContentModification(filePath, RawInputUtil.create(newFileContent));
@@ -99,7 +102,7 @@
 
   private String getFileContent(
       Repository repository, ProjectState projectState, ObjectId patchSetCommitId, String filePath)
-      throws ResourceNotFoundException, IOException {
+      throws ResourceNotFoundException, BadRequestException, IOException {
     try (BinaryResult fileContent =
         fileContentUtil.getContent(repository, projectState, patchSetCommitId, filePath)) {
       return fileContent.asString();
diff --git a/java/com/google/gerrit/server/git/DelegateRepository.java b/java/com/google/gerrit/server/git/DelegateRepository.java
index 800490d..b61488b 100644
--- a/java/com/google/gerrit/server/git/DelegateRepository.java
+++ b/java/com/google/gerrit/server/git/DelegateRepository.java
@@ -81,7 +81,8 @@
   @SuppressWarnings("rawtypes")
   private static BaseRepositoryBuilder toBuilder(Repository repo) {
     if (!repo.isBare()) {
-      throw new IllegalArgumentException("non-bare repository is not supported");
+      throw new IllegalArgumentException(
+          "non-bare repository is not supported: " + repo.getIdentifier());
     }
 
     return new BaseRepositoryBuilder<>().setFS(repo.getFS()).setGitDir(repo.getDirectory());
diff --git a/java/com/google/gerrit/server/logging/Metadata.java b/java/com/google/gerrit/server/logging/Metadata.java
index 7af204e..60e41ed 100644
--- a/java/com/google/gerrit/server/logging/Metadata.java
+++ b/java/com/google/gerrit/server/logging/Metadata.java
@@ -92,6 +92,9 @@
   // The version of a secondary index.
   public abstract Optional<Integer> indexVersion();
 
+  // The number of inputs to an operation, eg. Reachable.fromRefs.
+  public abstract Optional<Integer> inputSize();
+
   // The name of the implementation method.
   public abstract Optional<String> methodName();
 
@@ -295,6 +298,8 @@
 
     public abstract Builder indexVersion(int indexVersion);
 
+    public abstract Builder inputSize(int size);
+
     public abstract Builder methodName(@Nullable String methodName);
 
     public abstract Builder multiple(boolean multiple);
diff --git a/java/com/google/gerrit/server/project/Reachable.java b/java/com/google/gerrit/server/project/Reachable.java
index 4ea5d11..c30378b 100644
--- a/java/com/google/gerrit/server/project/Reachable.java
+++ b/java/com/google/gerrit/server/project/Reachable.java
@@ -17,6 +17,9 @@
 import com.google.common.flogger.FluentLogger;
 import com.google.gerrit.entities.Project;
 import com.google.gerrit.server.change.IncludedInResolver;
+import com.google.gerrit.server.logging.Metadata;
+import com.google.gerrit.server.logging.TraceContext;
+import com.google.gerrit.server.logging.TraceContext.TraceTimer;
 import com.google.gerrit.server.permissions.PermissionBackend;
 import com.google.gerrit.server.permissions.PermissionBackend.RefFilterOptions;
 import com.google.gerrit.server.permissions.PermissionBackendException;
@@ -58,7 +61,15 @@
               .currentUser()
               .project(project)
               .filter(refs, repo, RefFilterOptions.defaults());
-      return IncludedInResolver.includedInAny(repo, rw, commit, filtered.values());
+
+      // The filtering above already produces a voluminous trace. To separate the permission check
+      // from the reachability check, do the trace here:
+      try (TraceTimer timer =
+          TraceContext.newTimer(
+              "IncludedInResolver.includedInAny",
+              Metadata.builder().projectName(project.get()).inputSize(refs.size()).build())) {
+        return IncludedInResolver.includedInAny(repo, rw, commit, filtered.values());
+      }
     } catch (IOException | PermissionBackendException e) {
       logger.atSevere().withCause(e).log(
           "Cannot verify permissions to commit object %s in repository %s", commit.name(), project);
diff --git a/java/com/google/gerrit/server/restapi/change/ApplyFix.java b/java/com/google/gerrit/server/restapi/change/ApplyFix.java
index d31fd92..74c5bc2 100644
--- a/java/com/google/gerrit/server/restapi/change/ApplyFix.java
+++ b/java/com/google/gerrit/server/restapi/change/ApplyFix.java
@@ -18,6 +18,7 @@
 import com.google.gerrit.entities.Project;
 import com.google.gerrit.extensions.common.EditInfo;
 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.Response;
@@ -65,8 +66,8 @@
 
   @Override
   public Response<EditInfo> apply(FixResource fixResource, Void nothing)
-      throws AuthException, ResourceConflictException, IOException, ResourceNotFoundException,
-          PermissionBackendException {
+      throws AuthException, BadRequestException, ResourceConflictException, IOException,
+          ResourceNotFoundException, PermissionBackendException {
     RevisionResource revisionResource = fixResource.getRevisionResource();
     Project.NameKey project = revisionResource.getProject();
     ProjectState projectState = projectCache.checkedGet(project);
diff --git a/java/com/google/gerrit/server/restapi/change/ListChangeMessages.java b/java/com/google/gerrit/server/restapi/change/ListChangeMessages.java
index bfc9f12..099d0a6 100644
--- a/java/com/google/gerrit/server/restapi/change/ListChangeMessages.java
+++ b/java/com/google/gerrit/server/restapi/change/ListChangeMessages.java
@@ -32,18 +32,19 @@
 @Singleton
 public class ListChangeMessages implements RestReadView<ChangeResource> {
   private final ChangeMessagesUtil changeMessagesUtil;
-  private final AccountLoader accountLoader;
+  private final AccountLoader.Factory accountLoaderFactory;
 
   @Inject
   public ListChangeMessages(
       ChangeMessagesUtil changeMessagesUtil, AccountLoader.Factory accountLoaderFactory) {
     this.changeMessagesUtil = changeMessagesUtil;
-    this.accountLoader = accountLoaderFactory.create(true);
+    this.accountLoaderFactory = accountLoaderFactory;
   }
 
   @Override
   public Response<List<ChangeMessageInfo>> apply(ChangeResource resource)
       throws PermissionBackendException {
+    AccountLoader accountLoader = accountLoaderFactory.create(true);
     List<ChangeMessage> messages = changeMessagesUtil.byChange(resource.getNotes());
     List<ChangeMessageInfo> messageInfos =
         messages.stream()
diff --git a/java/com/google/gerrit/server/restapi/group/ListMembers.java b/java/com/google/gerrit/server/restapi/group/ListMembers.java
index 6e9f479..23f0aa7 100644
--- a/java/com/google/gerrit/server/restapi/group/ListMembers.java
+++ b/java/com/google/gerrit/server/restapi/group/ListMembers.java
@@ -45,7 +45,7 @@
 public class ListMembers implements RestReadView<GroupResource> {
   private final GroupCache groupCache;
   private final GroupControl.Factory groupControlFactory;
-  private final AccountLoader accountLoader;
+  private final AccountLoader.Factory accountLoaderFactory;
 
   @Option(name = "--recursive", usage = "to resolve included groups recursively")
   private boolean recursive;
@@ -57,7 +57,7 @@
       AccountLoader.Factory accountLoaderFactory) {
     this.groupCache = groupCache;
     this.groupControlFactory = groupControlFactory;
-    this.accountLoader = accountLoaderFactory.create(true);
+    this.accountLoaderFactory = accountLoaderFactory;
   }
 
   public ListMembers setRecursive(boolean recursive) {
@@ -112,6 +112,7 @@
 
   private List<AccountInfo> toAccountInfos(Set<Account.Id> members)
       throws PermissionBackendException {
+    AccountLoader accountLoader = accountLoaderFactory.create(true);
     List<AccountInfo> memberInfos = new ArrayList<>(members.size());
     for (Account.Id member : members) {
       memberInfos.add(accountLoader.get(member));
diff --git a/javatests/com/google/gerrit/acceptance/api/revision/RevisionIT.java b/javatests/com/google/gerrit/acceptance/api/revision/RevisionIT.java
index 32941ff..ad73e0f 100644
--- a/javatests/com/google/gerrit/acceptance/api/revision/RevisionIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/revision/RevisionIT.java
@@ -1328,6 +1328,23 @@
   }
 
   @Test
+  public void cannotGetContentOfDirectory() throws Exception {
+    Map<String, String> files = ImmutableMap.of("dir/file1.txt", "content 1");
+    PushOneCommit.Result result =
+        pushFactory.create(admin.newIdent(), testRepo, SUBJECT, files).to("refs/for/master");
+    result.assertOkStatus();
+
+    assertThrows(
+        BadRequestException.class,
+        () ->
+            gApi.changes()
+                .id(result.getChangeId())
+                .revision(result.getCommit().name())
+                .file("dir")
+                .content());
+  }
+
+  @Test
   public void contentType() throws Exception {
     PushOneCommit.Result r = createChange();
 
diff --git a/javatests/com/google/gerrit/server/account/externalids/ExternalIDCacheLoaderTest.java b/javatests/com/google/gerrit/server/account/externalids/ExternalIDCacheLoaderTest.java
index 054b1aa..2f64ed0 100644
--- a/javatests/com/google/gerrit/server/account/externalids/ExternalIDCacheLoaderTest.java
+++ b/javatests/com/google/gerrit/server/account/externalids/ExternalIDCacheLoaderTest.java
@@ -18,9 +18,9 @@
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyZeroInteractions;
-import static org.mockito.Mockito.when;
 
 import com.google.common.cache.Cache;
+import com.google.common.cache.CacheBuilder;
 import com.google.common.collect.ImmutableList;
 import com.google.gerrit.entities.Account;
 import com.google.gerrit.entities.RefNames;
@@ -44,7 +44,6 @@
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.mockito.Mock;
 import org.mockito.Mockito;
 import org.mockito.junit.MockitoJUnitRunner;
 
@@ -52,8 +51,7 @@
 public class ExternalIDCacheLoaderTest {
   private static AllUsersName ALL_USERS = new AllUsersName(AllUsersNameProvider.DEFAULT);
 
-  @Mock Cache<ObjectId, AllExternalIds> externalIdCache;
-
+  private Cache<ObjectId, AllExternalIds> externalIdCache;
   private ExternalIdCacheLoader loader;
   private GitRepositoryManager repoManager = new InMemoryRepositoryManager();
   private ExternalIdReader externalIdReader;
@@ -61,6 +59,7 @@
 
   @Before
   public void setUp() throws Exception {
+    externalIdCache = CacheBuilder.newBuilder().build();
     repoManager.createRepository(ALL_USERS).close();
     externalIdReader = new ExternalIdReader(repoManager, ALL_USERS, new DisabledMetricMaker());
     externalIdReaderSpy = Mockito.spy(externalIdReader);
@@ -78,8 +77,7 @@
   public void reloadsSingleUpdateUsingPartialReload() throws Exception {
     ObjectId firstState = insertExternalId(1, 1);
     ObjectId head = insertExternalId(2, 2);
-
-    when(externalIdCache.getIfPresent(firstState)).thenReturn(allFromGit(firstState));
+    externalIdCache.put(firstState, allFromGit(firstState));
 
     assertThat(loader.load(head)).isEqualTo(allFromGit(head));
     verifyZeroInteractions(externalIdReaderSpy);
@@ -91,8 +89,7 @@
     insertExternalId(2, 2);
     insertExternalId(3, 3);
     ObjectId head = insertExternalId(4, 4);
-
-    when(externalIdCache.getIfPresent(firstState)).thenReturn(allFromGit(firstState));
+    externalIdCache.put(firstState, allFromGit(firstState));
 
     assertThat(loader.load(head)).isEqualTo(allFromGit(head));
     verifyZeroInteractions(externalIdReaderSpy);
@@ -100,11 +97,9 @@
 
   @Test
   public void reloadsAllExternalIdsWhenNoOldStateIsCached() throws Exception {
-    ObjectId firstState = insertExternalId(1, 1);
+    insertExternalId(1, 1);
     ObjectId head = insertExternalId(2, 2);
 
-    when(externalIdCache.getIfPresent(firstState)).thenReturn(null);
-
     assertThat(loader.load(head)).isEqualTo(allFromGit(head));
     verify(externalIdReaderSpy, times(1)).all(head);
   }
@@ -144,8 +139,7 @@
     ObjectId firstState = insertExternalId(1, 1);
     ObjectId head = deleteExternalId(1, 1);
     assertThat(allFromGit(head).byAccount().size()).isEqualTo(0);
-
-    when(externalIdCache.getIfPresent(firstState)).thenReturn(allFromGit(firstState));
+    externalIdCache.put(firstState, allFromGit(firstState));
 
     assertThat(loader.load(head)).isEqualTo(allFromGit(head));
     verifyZeroInteractions(externalIdReaderSpy);
@@ -159,8 +153,7 @@
             externalId(1, 1),
             ExternalId.create("fooschema", "bar1", Account.id(1), "foo@bar.com", "password"));
     assertThat(allFromGit(head).byAccount().size()).isEqualTo(1);
-
-    when(externalIdCache.getIfPresent(firstState)).thenReturn(allFromGit(firstState));
+    externalIdCache.put(firstState, allFromGit(firstState));
 
     assertThat(loader.load(head)).isEqualTo(allFromGit(head));
     verifyZeroInteractions(externalIdReaderSpy);
@@ -177,7 +170,7 @@
       head = repo.exactRef(RefNames.REFS_EXTERNAL_IDS).getObjectId();
     }
 
-    when(externalIdCache.getIfPresent(firstState)).thenReturn(allFromGit(firstState));
+    externalIdCache.put(firstState, allFromGit(firstState));
 
     assertThat(loader.load(head)).isEqualTo(allFromGit(head));
     verifyZeroInteractions(externalIdReaderSpy);
@@ -190,8 +183,7 @@
     ObjectId oldState = inserExternalIds(257);
     assertAllFilesHaveSlashesInPath();
     ObjectId head = insertExternalId(500, 500);
-
-    when(externalIdCache.getIfPresent(oldState)).thenReturn(allFromGit(oldState));
+    externalIdCache.put(oldState, allFromGit(oldState));
 
     assertThat(loader.load(head)).isEqualTo(allFromGit(head));
     verifyZeroInteractions(externalIdReaderSpy);
@@ -205,8 +197,7 @@
     // Create one more external ID and then have the Loader compute the new state
     ObjectId head = insertExternalId(500, 500);
     assertAllFilesHaveSlashesInPath(); // NoteMap resharded
-
-    when(externalIdCache.getIfPresent(oldState)).thenReturn(allFromGit(oldState));
+    externalIdCache.put(oldState, allFromGit(oldState));
 
     assertThat(loader.load(head)).isEqualTo(allFromGit(head));
     verifyZeroInteractions(externalIdReaderSpy);
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 e9fcf65..c75227c 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
@@ -512,7 +512,7 @@
       },
 
       // Alias for getKeyboardEvent.
-      /** @return {!(Event|PolymerDomApi|PolymerEventApi)} */
+      /** @return {!Event} */
       getKeyboardEvent(e) {
         return getKeyboardEvent(e);
       },
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 6b46cef..e0b054b 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
@@ -19,7 +19,6 @@
 
   Polymer({
     is: 'gr-repo-list',
-    _legacyUndefinedCheck: true,
 
     properties: {
       /**
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 da0c98d..e32c773 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
@@ -369,7 +369,6 @@
     },
 
     _getListItems() {
-      // Polymer2: querySelectorAll returns NodeList instead of Array.
       return Array.from(
           Polymer.dom(this.root).querySelectorAll('gr-change-list-item'));
     },
diff --git a/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.html b/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.html
index 46b79b1..4783509 100644
--- a/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.html
+++ b/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.html
@@ -36,6 +36,7 @@
 <link rel="import" href="../gr-confirm-move-dialog/gr-confirm-move-dialog.html">
 <link rel="import" href="../gr-confirm-rebase-dialog/gr-confirm-rebase-dialog.html">
 <link rel="import" href="../gr-confirm-revert-dialog/gr-confirm-revert-dialog.html">
+<link rel="import" href="../gr-confirm-revert-submission-dialog/gr-confirm-revert-submission-dialog.html">
 <link rel="import" href="../gr-confirm-submit-dialog/gr-confirm-submit-dialog.html">
 <link rel="import" href="../../../styles/shared-styles.html">
 
@@ -208,6 +209,11 @@
           on-confirm="_handleRevertDialogConfirm"
           on-cancel="_handleConfirmDialogCancel"
           hidden></gr-confirm-revert-dialog>
+      <gr-confirm-revert-submission-dialog id="confirmRevertSubmissionDialog"
+          class="confirmDialog"
+          on-confirm="_handleRevertSubmissionDialogConfirm"
+          on-cancel="_handleConfirmDialogCancel"
+          hidden></gr-confirm-revert-submission-dialog>
       <gr-confirm-abandon-dialog id="confirmAbandonDialog"
           class="confirmDialog"
           on-confirm="_handleAbandonDialogConfirm"
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 8fa9f60..c8466df 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
@@ -50,7 +50,6 @@
     OPTIONAL: 'OPTIONAL',
   };
 
-  // TODO(davido): Add the rest of the change actions.
   const ChangeActions = {
     ABANDON: 'abandon',
     DELETE: '/',
@@ -65,6 +64,7 @@
     REBASE_EDIT: 'rebaseEdit',
     RESTORE: 'restore',
     REVERT: 'revert',
+    REVERT_SUBMISSION: 'revert_submission',
     REVIEWED: 'reviewed',
     STOP_EDIT: 'stopEdit',
     UNIGNORE: 'unignore',
@@ -72,7 +72,6 @@
     WIP: 'wip',
   };
 
-  // TODO(andybons): Add the rest of the revision actions.
   const RevisionActions = {
     CHERRYPICK: 'cherrypick',
     REBASE: 'rebase',
@@ -88,6 +87,7 @@
     rebase: 'Rebasing...',
     restore: 'Restoring...',
     revert: 'Reverting...',
+    revert_submission: 'Reverting Submission...',
     submit: 'Submitting...',
   };
 
@@ -182,6 +182,7 @@
     ChangeActions.REBASE_EDIT,
     ChangeActions.RESTORE,
     ChangeActions.REVERT,
+    ChangeActions.REVERT_SUBMISSION,
     ChangeActions.STOP_EDIT,
     QUICK_APPROVE_ACTION.key,
     RevisionActions.REBASE,
@@ -908,6 +909,19 @@
       this._showActionDialog(this.$.confirmRevertDialog);
     },
 
+    _modifyRevertSubmissionMsg() {
+      return this.$.jsAPI.modifyRevertSubmissionMsg(this.change,
+          this.$.confirmRevertSubmissionDialog.message, this.commitMessage);
+    },
+
+    showRevertSubmissionDialog() {
+      this.$.confirmRevertSubmissionDialog.populateRevertSubmissionMessage(
+          this.commitMessage, this.change.current_revision);
+      this.$.confirmRevertSubmissionDialog.message =
+          this._modifyRevertSubmissionMsg();
+      this._showActionDialog(this.$.confirmRevertSubmissionDialog);
+    },
+
     _handleActionTap(e) {
       e.preventDefault();
       let el = Polymer.dom(e).localTarget;
@@ -958,6 +972,9 @@
         case ChangeActions.REVERT:
           this.showRevertDialog();
           break;
+        case ChangeActions.REVERT_SUBMISSION:
+          this.showRevertSubmissionDialog();
+          break;
         case ChangeActions.ABANDON:
           this._showActionDialog(this.$.confirmAbandonDialog);
           break;
@@ -1071,7 +1088,6 @@
     _handleCherryPickRestApi(conflicts) {
       const el = this.$.confirmCherrypick;
       if (!el.branch) {
-        // TODO(davido): Fix error handling
         this.fire('show-alert', {message: ERR_BRANCH_EMPTY});
         return;
       }
@@ -1121,6 +1137,14 @@
           {message: el.message});
     },
 
+    _handleRevertSubmissionDialogConfirm() {
+      const el = this.$.confirmRevertSubmissionDialog;
+      this.$.overlay.close();
+      el.hidden = true;
+      this._fireAction('/revert_submission', this.actions.revert_submission,
+          false, {message: el.message});
+    },
+
     _handleAbandonDialogConfirm() {
       const el = this.$.confirmAbandonDialog;
       this.$.overlay.close();
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 edbed0a8..07eb59e 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
@@ -17,17 +17,6 @@
 (function() {
   'use strict';
 
-  const Defs = {};
-
-  /**
-   * @typedef {{
-   *    message: string,
-   *    icon: string,
-   *    class: string,
-   *  }}
-   */
-  Defs.PushCertificateValidation;
-
   const HASHTAG_ADD_MESSAGE = 'Add Hashtag';
 
   const SubmitTypeLabel = {
@@ -100,7 +89,7 @@
         computed: '_computeHashtagReadOnly(_mutable, change)',
       },
       /**
-       * @type {Defs.PushCertificateValidation}
+       * @type {Gerrit.PushCertificateValidation}
        */
       _pushCertificateValidation: {
         type: Object,
@@ -295,7 +284,7 @@
     },
 
     /**
-     * @return {?Defs.PushCertificateValidation} object representing data for
+     * @return {?Gerrit.PushCertificateValidation} object representing data for
      *     the push validation.
      */
     _computePushCertificateValidation(serverConfig, change) {
diff --git a/polygerrit-ui/app/elements/change/gr-change-requirements/gr-change-requirements.html b/polygerrit-ui/app/elements/change/gr-change-requirements/gr-change-requirements.html
index 74af794..47ff7f7 100644
--- a/polygerrit-ui/app/elements/change/gr-change-requirements/gr-change-requirements.html
+++ b/polygerrit-ui/app/elements/change/gr-change-requirements/gr-change-requirements.html
@@ -76,12 +76,10 @@
         cursor: pointer;
       }
       .showHide .title {
-        border-top: 1px solid var(--border-color);
         padding-bottom: var(--spacing-m);
-        padding-top: var(--spacing-m);
+        padding-top: var(--spacing-l);
       }
       .showHide .value {
-        border-top: 1px solid var(--border-color);
         padding-top: 0;
         vertical-align: middle;
       }
diff --git a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.html b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.html
index a84a465..e36587b 100644
--- a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.html
+++ b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.html
@@ -362,7 +362,7 @@
     <div
         id="mainContent"
         class="container"
-        on-show-checks-table="_handleShowChecksTable"
+        on-show-checks-table="_handleShowTab"
         hidden$="{{_loading}}">
       <div class$="[[_computeHeaderClass(_editMode)]]">
         <div class="headerTitle">
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 2a6c18a..e6ceea2 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
@@ -415,15 +415,23 @@
       this._showMessagesView = this.$.commentTabs.selected === 0;
     },
 
-    _handleFileTabChange() {
+    _handleFileTabChange(e) {
       const selectedIndex = this.$$('#primaryTabs').selected;
       this._showFileTabContent = selectedIndex === 0;
       // Initial tab is the static files list.
-      this._selectedFilesTabPluginEndpoint =
+      const newSelectedTab =
           this._dynamicTabContentEndpoints[selectedIndex - 1];
+      if (newSelectedTab !== this._selectedFilesTabPluginEndpoint) {
+        this._selectedFilesTabPluginEndpoint = newSelectedTab;
+
+        const tabName = this._selectedFilesTabPluginEndpoint || 'files';
+        const source = e && e.type ? e.type : '';
+        this.$.reporting.reportInteraction('tab-changed',
+            `tabname: ${tabName}, source: ${source}`);
+      }
     },
 
-    _handleShowChecksTable(e) {
+    _handleShowTab(e) {
       const idx = this._dynamicTabContentEndpoints.indexOf(e.detail.tab);
       if (idx === -1) {
         console.warn(e.detail.tab + ' tab not found');
@@ -431,7 +439,7 @@
       }
       this.$$('#primaryTabs').selected = idx + 1;
       this.$$('#primaryTabs').scrollIntoView();
-      this._handleFileTabChange();
+      this.$.reporting.reportInteraction('show-tab', e.detail.tab);
     },
 
     _handleEditCommitMessage(e) {
diff --git a/polygerrit-ui/app/elements/change/gr-confirm-revert-submission-dialog/gr-confirm-revert-submission-dialog.html b/polygerrit-ui/app/elements/change/gr-confirm-revert-submission-dialog/gr-confirm-revert-submission-dialog.html
new file mode 100644
index 0000000..b334989
--- /dev/null
+++ b/polygerrit-ui/app/elements/change/gr-confirm-revert-submission-dialog/gr-confirm-revert-submission-dialog.html
@@ -0,0 +1,71 @@
+<!--
+@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.
+-->
+
+<link rel="import" href="/bower_components/iron-autogrow-textarea/iron-autogrow-textarea.html">
+<link rel="import" href="/bower_components/polymer/polymer.html">
+<link rel="import" href="../../../behaviors/fire-behavior/fire-behavior.html">
+<link rel="import" href="../../shared/gr-dialog/gr-dialog.html">
+<link rel="import" href="../../../styles/shared-styles.html">
+
+<dom-module id="gr-confirm-revert-submission-dialog">
+  <template>
+    <!-- TODO(taoalpha): move all shared styles to a style module. -->
+    <style include="shared-styles">
+      :host {
+        display: block;
+      }
+      :host([disabled]) {
+        opacity: .5;
+        pointer-events: none;
+      }
+      label {
+        cursor: pointer;
+        display: block;
+        width: 100%;
+      }
+      iron-autogrow-textarea {
+        font-family: var(--monospace-font-family);
+        padding: 0;
+        width: 73ch; /* Add a char to account for the border. */
+
+        --iron-autogrow-textarea {
+          border: 1px solid var(--border-color);
+          box-sizing: border-box;
+          font-family: var(--monospace-font-family);
+        }
+      }
+    </style>
+    <gr-dialog
+        confirm-label="Revert Submission"
+        on-confirm="_handleConfirmTap"
+        on-cancel="_handleCancelTap">
+      <div class="header" slot="header">Revert Submission</div>
+      <div class="main" slot="main">
+        <label for="messageInput">
+          Revert Commit Message
+        </label>
+        <iron-autogrow-textarea
+            id="messageInput"
+            class="message"
+            autocomplete="on"
+            max-rows="15"
+            bind-value="{{message}}"></iron-autogrow-textarea>
+      </div>
+    </gr-dialog>
+  </template>
+  <script src="gr-confirm-revert-submission-dialog.js"></script>
+</dom-module>
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
new file mode 100644
index 0000000..6cbbb37
--- /dev/null
+++ b/polygerrit-ui/app/elements/change/gr-confirm-revert-submission-dialog/gr-confirm-revert-submission-dialog.js
@@ -0,0 +1,69 @@
+/**
+ * @license
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+(function() {
+  'use strict';
+
+  const ERR_COMMIT_NOT_FOUND =
+      'Unable to find the commit hash of this change.';
+
+  Polymer({
+    is: 'gr-confirm-revert-submission-dialog',
+
+    /**
+     * Fired when the confirm button is pressed.
+     *
+     * @event confirm
+     */
+
+    /**
+     * Fired when the cancel button is pressed.
+     *
+     * @event cancel
+     */
+
+    properties: {
+      message: String,
+    },
+
+    behaviors: [
+      Gerrit.FireBehavior,
+    ],
+
+    populateRevertSubmissionMessage(message, commitHash) {
+      // Follow the same convention of the revert
+      const revertTitle = 'Revert submission';
+      if (!commitHash) {
+        this.fire('show-alert', {message: ERR_COMMIT_NOT_FOUND});
+        return;
+      }
+      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});
+    },
+  });
+})();
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
new file mode 100644
index 0000000..c0f2dc3
--- /dev/null
+++ b/polygerrit-ui/app/elements/change/gr-confirm-revert-submission-dialog/gr-confirm-revert-submission-dialog_test.html
@@ -0,0 +1,100 @@
+<!DOCTYPE html>
+<!--
+@license
+Copyright (C) 2019 The Android Open Source Project
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+
+<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
+<title>gr-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>
+<script src="/bower_components/web-component-tester/browser.js"></script>
+<link rel="import" href="../../../test/common-test-setup.html"/>
+<link rel="import" href="gr-confirm-revert-submission-dialog.html">
+
+<script>void(0);</script>
+
+<test-fixture id="basic">
+  <template>
+    <gr-confirm-revert-submission-dialog>
+    </gr-confirm-revert-submission-dialog>
+  </template>
+</test-fixture>
+
+<script>
+  suite('gr-confirm-revert-submission-dialog tests', () => {
+    let element;
+    let sandbox;
+
+    setup(() => {
+      element = fixture('basic');
+      sandbox =sinon.sandbox.create();
+    });
+
+    teardown(() => sandbox.restore());
+
+    test('no match', () => {
+      assert.isNotOk(element.message);
+      const alertStub = sandbox.stub();
+      element.addEventListener('show-alert', alertStub);
+      element.populateRevertSubmissionMessage(
+          'not a commitHash in sight'
+      );
+      assert.isTrue(alertStub.calledOnce);
+    });
+
+    test('single line', () => {
+      assert.isNotOk(element.message);
+      element.populateRevertSubmissionMessage(
+          'one line commit\n\nChange-Id: abcdefg\n',
+          'abcd123');
+      const expected = 'Revert submission\n\n' +
+        'Reason for revert: <INSERT REASONING HERE>\n';
+      assert.equal(element.message, expected);
+    });
+
+    test('multi line', () => {
+      assert.isNotOk(element.message);
+      element.populateRevertSubmissionMessage(
+          'many lines\ncommit\n\nmessage\n\nChange-Id: abcdefg\n',
+          'abcd123');
+      const expected = 'Revert submission\n\n' +
+        'Reason for revert: <INSERT REASONING HERE>\n';
+      assert.equal(element.message, expected);
+    });
+
+    test('issue above change id', () => {
+      assert.isNotOk(element.message);
+      element.populateRevertSubmissionMessage(
+          'test \nvery\n\ncommit\n\nBug: Issue 42\nChange-Id: abcdefg\n',
+          'abcd123');
+      const expected = 'Revert submission\n\n' +
+          'Reason for revert: <INSERT REASONING HERE>\n';
+      assert.equal(element.message, expected);
+    });
+
+    test('revert a revert', () => {
+      assert.isNotOk(element.message);
+      element.populateRevertSubmissionMessage(
+          'Revert "one line commit"\n\nChange-Id: abcdefg\n',
+          'abcd123');
+      const expected = 'Revert submission\n\n' +
+        'Reason for revert: <INSERT REASONING HERE>\n';
+      assert.equal(element.message, expected);
+    });
+  });
+</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 b52b715..4903467 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,26 +41,6 @@
     U: 'Unchanged',
   };
 
-  const Defs = {};
-
-  /**
-   * Object containing layout values to be used in rendering size-bars.
-   * `max{Inserted,Deleted}` represent the largest values of the
-   * `lines_inserted` and `lines_deleted` fields of the files respectively. The
-   * `max{Addition,Deletion}Width` represent the width of the graphic allocated
-   * to the insertion or deletion side respectively. Finally, the
-   * `deletionOffset` value represents the x-position for the deletion bar.
-   *
-   * @typedef {{
-   *    maxInserted: number,
-   *    maxDeleted: number,
-   *    maxAdditionWidth: number,
-   *    maxDeletionWidth: number,
-   *    deletionOffset: number,
-   * }}
-   */
-  Defs.LayoutStats;
-
   Polymer({
     is: 'gr-file-list',
 
@@ -165,7 +145,7 @@
         type: Boolean,
         observer: '_loadingChanged',
       },
-      /** @type {Defs.LayoutStats|undefined} */
+      /** @type {Gerrit.LayoutStats|undefined} */
       _sizeBarLayout: {
         type: Object,
         computed: '_computeSizeBarLayout(_shownFiles.*)',
@@ -332,7 +312,6 @@
     },
 
     get diffs() {
-      // Polymer2: querySelectorAll returns NodeList instead of Array.
       return Array.from(
           Polymer.dom(this.root).querySelectorAll('gr-diff-host'));
     },
@@ -926,7 +905,6 @@
     _filesChanged() {
       if (this._files && this._files.length > 0) {
         Polymer.dom.flush();
-        // Polymer2: querySelectorAll returns NodeList instead of Array.
         const files = Array.from(
             Polymer.dom(this.root).querySelectorAll('.file-row'));
         this.$.fileCursor.stops = files;
@@ -1197,7 +1175,7 @@
 
     /**
      * Compute size bar layout values from the file list.
-     * @return {Defs.LayoutStats|undefined}
+     * @return {Gerrit.LayoutStats|undefined}
      */
     _computeSizeBarLayout(shownFilesRecord) {
       if (!shownFilesRecord || !shownFilesRecord.base) { return undefined; }
@@ -1232,7 +1210,7 @@
     /**
      * Get the width of the addition bar for a file.
      * @param {Object} file
-     * @param {Defs.LayoutStats} stats
+     * @param {Gerrit.LayoutStats} stats
      * @return {number}
      */
     _computeBarAdditionWidth(file, stats) {
@@ -1249,7 +1227,7 @@
     /**
      * Get the x-offset of the addition bar for a file.
      * @param {Object} file
-     * @param {Defs.LayoutStats} stats
+     * @param {Gerrit.LayoutStats} stats
      * @return {number}
      */
     _computeBarAdditionX(file, stats) {
@@ -1260,7 +1238,7 @@
     /**
      * Get the width of the deletion bar for a file.
      * @param {Object} file
-     * @param {Defs.LayoutStats} stats
+     * @param {Gerrit.LayoutStats} stats
      * @return {number}
      */
     _computeBarDeletionWidth(file, stats) {
@@ -1276,7 +1254,7 @@
 
     /**
      * Get the x-offset of the deletion bar for a file.
-     * @param {Defs.LayoutStats} stats
+     * @param {Gerrit.LayoutStats} stats
      * @return {number}
      */
     _computeBarDeletionX(stats) {
diff --git a/polygerrit-ui/app/elements/change/gr-label-score-row/gr-label-score-row.html b/polygerrit-ui/app/elements/change/gr-label-score-row/gr-label-score-row.html
index b0d2838..220546b 100644
--- a/polygerrit-ui/app/elements/change/gr-label-score-row/gr-label-score-row.html
+++ b/polygerrit-ui/app/elements/change/gr-label-score-row/gr-label-score-row.html
@@ -108,6 +108,7 @@
           <span class="placeholder" data-label$="[[label.name]]"></span>
         </template>
         <iron-selector
+            id="labelSelector"
             attr-for-selected="value"
             selected="[[_computeLabelValue(labels, permittedLabels, label)]]"
             hidden$="[[!_computeAnyPermittedLabelValues(permittedLabels, label.name)]]"
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 2ea63b0..0b888c4 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
@@ -65,7 +65,7 @@
     },
 
     get _ironSelector() {
-      return this.$$('iron-selector');
+      return this.$ && this.$.labelSelector;
     },
 
     _computeBlankItems(permittedLabels, label, side) {
@@ -96,16 +96,24 @@
     },
 
     _computeButtonClass(value, index, totalItems) {
-      if (value < 0 && index === 0) {
-        return 'min';
-      } else if (value < 0) {
-        return 'negative';
-      } else if (value > 0 && index === totalItems - 1) {
-        return 'max';
-      } else if (value > 0) {
-        return 'positive';
+      const classes = [];
+      if (value === this.selectedValue) {
+        classes.push('iron-selected');
       }
-      return 'neutral';
+
+      if (value < 0 && index === 0) {
+        classes.push('min');
+      } else if (value < 0) {
+        classes.push('negative');
+      } else if (value > 0 && index === totalItems - 1) {
+        classes.push('max');
+      } else if (value > 0) {
+        classes.push('positive');
+      } else {
+        classes.push('neutral');
+      }
+
+      return classes.join(' ');
     },
 
     _computeLabelValue(labels, permittedLabels, label) {
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 5f7ccbe..519fbb8 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
@@ -108,7 +108,7 @@
     test('label picker', () => {
       const labelsChangedHandler = sandbox.stub();
       element.addEventListener('labels-changed', labelsChangedHandler);
-      assert.ok(element.$$('iron-selector'));
+      assert.ok(element.$.labelSelector);
       MockInteractions.tap(element.$$(
           'gr-button[value="-1"]'));
       flushAsynchronousOperations();
@@ -157,9 +157,9 @@
 
     test('correct item is selected', () => {
       // 1 should be the value of the selected item
-      assert.strictEqual(element.$$('iron-selector').selected, '+1');
+      assert.strictEqual(element.$.labelSelector.selected, '+1');
       assert.strictEqual(
-          element.$$('iron-selector').selectedItem
+          element.$.labelSelector.selectedItem
               .textContent.trim(), '+1');
       assert.strictEqual(
           element.$.selectedValueLabel.textContent.trim(), 'good');
@@ -236,7 +236,7 @@
           default_value: 0,
         },
       };
-      const selector = element.$$('iron-selector');
+      const selector = element.$.labelSelector;
       element.set('label', {name: 'Verified', value: ' 0'});
       flushAsynchronousOperations();
       assert.strictEqual(selector.selected, ' 0');
@@ -253,18 +253,18 @@
         ],
       };
       flushAsynchronousOperations();
-      assert.isOk(element.$$('iron-selector'));
-      assert.isFalse(element.$$('iron-selector').hidden);
+      assert.isOk(element.$.labelSelector);
+      assert.isFalse(element.$.labelSelector.hidden);
 
       element.permittedLabels = {};
       flushAsynchronousOperations();
-      assert.isOk(element.$$('iron-selector'));
-      assert.isTrue(element.$$('iron-selector').hidden);
+      assert.isOk(element.$.labelSelector);
+      assert.isTrue(element.$.labelSelector.hidden);
 
       element.permittedLabels = {Verified: []};
       flushAsynchronousOperations();
-      assert.isOk(element.$$('iron-selector'));
-      assert.isTrue(element.$$('iron-selector').hidden);
+      assert.isOk(element.$.labelSelector);
+      assert.isTrue(element.$.labelSelector.hidden);
     });
 
     test('asymetrical labels', done => {
@@ -282,7 +282,7 @@
         ],
       };
       flush(() => {
-        assert.strictEqual(element.$$('iron-selector')
+        assert.strictEqual(element.$.labelSelector
             .items.length, 2);
         assert.strictEqual(
             Polymer.dom(element.root).querySelectorAll('.placeholder').length,
@@ -302,7 +302,7 @@
           ],
         };
         flush(() => {
-          assert.strictEqual(element.$$('iron-selector')
+          assert.strictEqual(element.$.labelSelector
               .items.length, 5);
           assert.strictEqual(
               Polymer.dom(element.root).querySelectorAll('.placeholder').length,
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 45f718f..3632348 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
@@ -120,12 +120,12 @@
       // resolving.
       sandbox.stub(element, '_purgeReviewersPendingRemove');
 
-      element.$$('#ccs').$.entry.setText('test');
+      element.$.ccs.$.entry.setText('test');
       MockInteractions.tap(element.$$('gr-button.send'));
       assert.isFalse(sendStub.called);
       flushAsynchronousOperations();
 
-      element.$$('#ccs').$.entry.setText('test@test.test');
+      element.$.ccs.$.entry.setText('test@test.test');
       MockInteractions.tap(element.$$('gr-button.send'));
       assert.isTrue(sendStub.called);
     });
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 a78e68f..cc7fbe6 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
@@ -388,9 +388,6 @@
      *
      * @param {!Object} account
      * @param {string} type
-     *
-     * * TODO(beckysiegel) submit Polymer PR
-     * @suppress {checkTypes}
      */
     _removeAccount(account, type) {
       if (account._pendingAdd) { return; }
@@ -402,7 +399,7 @@
             const reviewers = this.change.reviewers[type] || [];
             for (let i = 0; i < reviewers.length; i++) {
               if (reviewers[i]._account_id == account._account_id) {
-                this.splice(['change', 'reviewers', type], i, 1);
+                this.splice(`change.reviewers.${type}`, i, 1);
                 break;
               }
             }
@@ -445,7 +442,7 @@
         }
         return this._mapReviewer(reviewer);
       });
-      const ccsEl = this.$$('#ccs');
+      const ccsEl = this.$.ccs;
       if (ccsEl) {
         for (let reviewer of ccsEl.additions()) {
           if (reviewer.account) {
@@ -497,7 +494,7 @@
         const reviewerEntry = this.$.reviewers.focusStart;
         reviewerEntry.async(reviewerEntry.focus);
       } else if (section === FocusTarget.CCS) {
-        const ccEntry = this.$$('#ccs').focusStart;
+        const ccEntry = this.$.ccs.focusStart;
         ccEntry.async(ccEntry.focus);
       }
     },
@@ -673,7 +670,7 @@
 
     _saveTapHandler(e) {
       e.preventDefault();
-      if (!this.$$('#ccs').submitEntryText()) {
+      if (!this.$.ccs.submitEntryText()) {
         // Do not proceed with the save if there is an invalid email entry in
         // the text field of the CC entry.
         return;
@@ -689,7 +686,7 @@
     },
 
     _submit() {
-      if (!this.$$('#ccs').submitEntryText()) {
+      if (!this.$.ccs.submitEntryText()) {
         // Do not proceed with the send if there is an invalid email entry in
         // the text field of the CC entry.
         return;
@@ -732,7 +729,7 @@
 
     _confirmPendingReviewer() {
       if (this._ccPendingConfirmation) {
-        this.$$('#ccs').confirmGroup(this._ccPendingConfirmation.group);
+        this.$.ccs.confirmGroup(this._ccPendingConfirmation.group);
         this._focusOn(FocusTarget.CCS);
       } else {
         this.$.reviewers.confirmGroup(this._reviewerPendingConfirmation.group);
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 99b91b7..db98041 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
@@ -389,7 +389,7 @@
             isFocusInsideElement(element.$.reviewers.$.entry.$.input.$.input));
 
         // No reviewer/CC should have been added.
-        assert.equal(element.$$('#ccs').additions().length, 0);
+        assert.equal(element.$.ccs.additions().length, 0);
         assert.equal(element.$.reviewers.additions().length, 0);
 
         // Reopen confirmation dialog.
@@ -414,7 +414,7 @@
       }).then(() => {
         assert.isFalse(isVisible(element.$.reviewerConfirmationOverlay));
         const additions = cc ?
-            element.$$('#ccs').additions() :
+            element.$.ccs.additions() :
             element.$.reviewers.additions();
         assert.deepEqual(
             additions,
@@ -459,9 +459,9 @@
     test('_reviewersMutated when account-text-change is fired from ccs', () => {
       flushAsynchronousOperations();
       assert.isFalse(element._reviewersMutated);
-      assert.isTrue(element.$$('#ccs').allowAnyInput);
+      assert.isTrue(element.$.ccs.allowAnyInput);
       assert.isFalse(element.$$('#reviewers').allowAnyInput);
-      element.$$('#ccs').dispatchEvent(new CustomEvent('account-text-changed',
+      element.$.ccs.dispatchEvent(new CustomEvent('account-text-changed',
           {bubbles: true, composed: true}));
       assert.isTrue(element._reviewersMutated);
     });
@@ -595,7 +595,7 @@
       const textareaStub = sandbox.stub(element.$.textarea, 'async');
       const reviewerEntryStub = sandbox.stub(element.$.reviewers.focusStart,
           'async');
-      const ccStub = sandbox.stub(element.$$('#ccs').focusStart, 'async');
+      const ccStub = sandbox.stub(element.$.ccs.focusStart, 'async');
       element._focusOn();
       assert.equal(element._chooseFocusTarget.callCount, 1);
       assert.deepEqual(textareaStub.callCount, 1);
@@ -803,7 +803,7 @@
       };
       flushAsynchronousOperations();
       const reviewers = element.$.reviewers;
-      const ccs = element.$$('#ccs');
+      const ccs = element.$.ccs;
       const reviewer1 = makeAccount();
       const reviewer2 = makeAccount();
       const cc1 = makeAccount();
diff --git a/polygerrit-ui/app/elements/core/gr-main-header/gr-main-header.html b/polygerrit-ui/app/elements/core/gr-main-header/gr-main-header.html
index 13629f2..fe8384e 100644
--- a/polygerrit-ui/app/elements/core/gr-main-header/gr-main-header.html
+++ b/polygerrit-ui/app/elements/core/gr-main-header/gr-main-header.html
@@ -46,7 +46,6 @@
       .bigTitle:hover {
         text-decoration: underline;
       }
-      /* TODO (viktard): Clean-up after chromium-style migrates to component. */
       .titleText::before {
         background-image: var(--header-icon);
         background-size: var(--header-icon-size) var(--header-icon-size);
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 70bda79..cef7e5b 100644
--- a/polygerrit-ui/app/elements/core/gr-router/gr-router.js
+++ b/polygerrit-ui/app/elements/core/gr-router/gr-router.js
@@ -1416,9 +1416,7 @@
       }
     },
 
-    // TODO fix this so it properly redirects
-    // to /settings#Agreements (Scrolls down)
-    _handleAgreementsRoute(data) {
+    _handleAgreementsRoute() {
       this._redirect('/settings/#Agreements');
     },
 
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 c3319ba..efed78d 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
@@ -19,35 +19,6 @@
 
   const PARENT = 'PARENT';
 
-  const Defs = {};
-
-  /**
-   * @typedef {{
-   *    basePatchNum: (string|number),
-   *    patchNum: (number),
-   * }}
-   */
-  Defs.patchRange;
-
-  /**
-   * @typedef {{
-   *    changeNum: number,
-   *    path: string,
-   *    patchRange: !Defs.patchRange,
-   *    projectConfig: (Object|undefined),
-   * }}
-   */
-  Defs.commentMeta;
-
-  /**
-   * @typedef {{
-   *    meta: !Defs.commentMeta,
-   *    left: !Array,
-   *    right: !Array,
-   * }}
-   */
-  Defs.commentsBySide;
-
   /**
    * Construct a change comments object, which can be data-bound to child
    * elements of that which uses the gr-comment-api.
@@ -92,7 +63,7 @@
    * Paths with comments are mapped to true, whereas paths without comments
    * are not mapped.
    *
-   * @param {Defs.patchRange=} opt_patchRange The patch-range object containing
+   * @param {Gerrit.PatchRange=} opt_patchRange The patch-range object containing
    *     patchNum and basePatchNum properties to represent the range.
    * @return {!Object}
    */
@@ -251,11 +222,11 @@
    * arrays of comments in on either side of the patch range for that path.
    *
    * @param {!string} path
-   * @param {!Defs.patchRange} patchRange The patch-range object containing patchNum
+   * @param {!Gerrit.PatchRange} patchRange The patch-range object containing patchNum
    *     and basePatchNum properties to represent the range.
    * @param {Object=} opt_projectConfig Optional project config object to
    *     include in the meta sub-object.
-   * @return {!Defs.commentsBySide}
+   * @return {!Gerrit.CommentsBySide}
    */
   ChangeComments.prototype.getCommentsBySideForPath = function(path,
       patchRange, opt_projectConfig) {
@@ -438,7 +409,7 @@
   * Whether the given comment should be included in the base side of the
   * given patch range.
   * @param {!Object} comment
-  * @param {!Defs.patchRange} range
+  * @param {!Gerrit.PatchRange} range
   * @return {boolean}
   */
   ChangeComments.prototype._isInBaseOfPatchRange = function(comment, range) {
@@ -469,7 +440,7 @@
    * Whether the given comment should be included in the revision side of the
    * given patch range.
    * @param {!Object} comment
-   * @param {!Defs.patchRange} range
+   * @param {!Gerrit.PatchRange} range
    * @return {boolean}
    */
   ChangeComments.prototype._isInRevisionOfPatchRange = function(comment,
@@ -481,7 +452,7 @@
   /**
    * Whether the given comment should be included in the given patch range.
    * @param {!Object} comment
-   * @param {!Defs.patchRange} range
+   * @param {!Gerrit.PatchRange} range
    * @return {boolean|undefined}
    */
   ChangeComments.prototype._isInPatchRange = function(comment, range) {
diff --git a/polygerrit-ui/app/elements/diff/gr-coverage-layer/gr-coverage-layer.html b/polygerrit-ui/app/elements/diff/gr-coverage-layer/gr-coverage-layer.html
index d743d92..549bf43 100644
--- a/polygerrit-ui/app/elements/diff/gr-coverage-layer/gr-coverage-layer.html
+++ b/polygerrit-ui/app/elements/diff/gr-coverage-layer/gr-coverage-layer.html
@@ -19,6 +19,7 @@
 <dom-module id="gr-coverage-layer">
   <template>
   </template>
+  <script src="../../../types/types.js"></script>
   <script src="../gr-diff-highlight/gr-annotation.js"></script>
   <script src="gr-coverage-layer.js"></script>
 </dom-module>
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 e8d6900..3d9c172 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
@@ -17,27 +17,6 @@
 (function() {
   'use strict';
 
-  /** @enum {string} */
-  Gerrit.CoverageType = {
-    /**
-     * start_character and end_character of the range will be ignored for this
-     * type.
-     */
-    COVERED: 'COVERED',
-    /**
-     * start_character and end_character of the range will be ignored for this
-     * type.
-     */
-    NOT_COVERED: 'NOT_COVERED',
-    PARTIALLY_COVERED: 'PARTIALLY_COVERED',
-    /**
-     * You don't have to use this. If there is no coverage information for a
-     * range, then it implicitly means NOT_INSTRUMENTED. start_character and
-     * end_character of the range will be ignored for this type.
-     */
-    NOT_INSTRUMENTED: 'NOT_INSTRUMENTED',
-  };
-
   const TOOLTIP_MAP = new Map([
     [Gerrit.CoverageType.COVERED, 'Covered by tests.'],
     [Gerrit.CoverageType.NOT_COVERED, 'Not covered by tests.'],
@@ -45,15 +24,6 @@
     [Gerrit.CoverageType.NOT_INSTRUMENTED, 'Not instrumented by any tests.'],
   ]);
 
-  /**
-   * @typedef {{
-   *   side: string,
-   *   type: Gerrit.CoverageType,
-   *   code_range: Gerrit.Range,
-   * }}
-   */
-  Gerrit.CoverageRange;
-
   Polymer({
     is: 'gr-coverage-layer',
 
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder.html b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder.html
index 0a00a01..69c2419 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder.html
@@ -16,7 +16,6 @@
 -->
 <link rel="import" href="/bower_components/polymer/polymer.html">
 <link rel="import" href="../../../behaviors/fire-behavior/fire-behavior.html">
-<link rel="import" href="../../shared/gr-js-api-interface/gr-js-api-interface.html">
 <link rel="import" href="../gr-coverage-layer/gr-coverage-layer.html">
 <link rel="import" href="../gr-diff-processor/gr-diff-processor.html">
 <link rel="import" href="../gr-ranged-comment-layer/gr-ranged-comment-layer.html">
@@ -40,7 +39,6 @@
     <gr-diff-processor
         id="processor"
         groups="{{_groups}}"></gr-diff-processor>
-    <gr-js-api-interface id="jsAPI"></gr-js-api-interface>
   </template>
   <script src="../../../scripts/util.js"></script>
   <script src="../gr-diff/gr-diff-line.js"></script>
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder.js b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder.js
index c25e90c..07d0c1f 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder.js
@@ -292,6 +292,7 @@
       e.detail = {
         groups,
         section,
+        numLines,
       };
       // Let it bubble up the DOM tree.
     });
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 45de810..3fdf242 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
@@ -30,6 +30,7 @@
 <script src="../gr-diff-highlight/gr-annotation.js"></script>
 <script src="gr-diff-builder.js"></script>
 
+<link rel="import" href="../../shared/gr-rest-api-interface/gr-rest-api-interface.html">
 <link rel="import" href="../../shared/gr-rest-api-interface/mock-diff-response_test.html">
 <link rel="import" href="gr-diff-builder.html">
 
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 ffb5efa..1c1100d 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
@@ -28,6 +28,7 @@
 
 <link rel="import" href="../gr-diff/gr-diff.html">
 <link rel="import" href="./gr-diff-cursor.html">
+<link rel="import" href="../../shared/gr-rest-api-interface/gr-rest-api-interface.html">
 <link rel="import" href="../../shared/gr-rest-api-interface/mock-diff-response_test.html">
 
 <script>void(0);</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 21a10fd..451bef6 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
@@ -203,9 +203,7 @@
       },
 
       /**
-       * TODO(brohlfs): Replace Object type by Gerrit.CoverageRange.
-       *
-       * @type {!Array<!Object>}
+       * @type {!Array<!Gerrit.CoverageRange>}
        */
       _coverageRanges: {
         type: Array,
@@ -252,6 +250,7 @@
       'render-content': '_handleRenderContent',
 
       'normalize-range': '_handleNormalizeRange',
+      'diff-context-expanded': '_handleDiffContextExpanded',
     },
 
     observers: [
@@ -427,7 +426,6 @@
      * @return {!Array<!HTMLElement>}
      */
     getThreadEls() {
-      // Polymer2: querySelectorAll returns NodeList instead of Array.
       return Array.from(
           Polymer.dom(this.$.diff).querySelectorAll('.comment-thread'));
     },
@@ -965,5 +963,10 @@
           `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);
+    },
   });
 })();
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 87ae7f2..4716544 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
@@ -19,44 +19,6 @@
 
   const WHOLE_FILE = -1;
 
-  const Defs = {};
-
-  /**
-   * The DiffIntralineInfo entity contains information about intraline edits in a
-   * file.
-   *
-   * The information consists of a list of <skip length, mark length> pairs, where
-   * the skip length is the number of characters between the end of the previous
-   * edit and the start of this edit, and the mark length is the number of edited
-   * characters following the skip. The start of the edits is from the beginning
-   * of the related diff content lines.
-   *
-   * Note that the implied newline character at the end of each line is included
-   * in the length calculation, and thus it is possible for the edits to span
-   * newlines.
-   * @typedef {!Array<number>}
-   */
-  Defs.IntralineInfo;
-
-  /**
-   * A portion of the diff that is treated the same.
-   *
-   * Called `DiffContent` in the API, see
-   * https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#diff-content
-   *
-   * @typedef {{
-   *  ab: ?Array<!string>,
-   *  a: ?Array<!string>,
-   *  b: ?Array<!string>,
-   *  skip: ?number,
-   *  edit_a: ?Array<!Defs.IntralineInfo>,
-   *  edit_b: ?Array<!Defs.IntralineInfo>,
-   *  due_to_rebase: ?boolean,
-   *  common: ?boolean
-   * }}
-   */
-  Defs.Chunk;
-
   const DiffSide = {
     LEFT: 'left',
     RIGHT: 'right',
@@ -171,7 +133,7 @@
     /**
      * Asynchronously process the diff chunks into groups. As it processes, it
      * will splice groups into the `groups` property of the component.
-     * @param {!Array<!Defs.Chunk>} chunks
+     * @param {!Array<!Gerrit.DiffChunk>} chunks
      * @param {boolean} isBinary
      * @return {!Promise<!Array<!Object>>} A promise that resolves with an
      *     array of GrDiffGroups when the diff is completely processed.
@@ -411,7 +373,7 @@
      * @param {string} lineType (GrDiffLine.Type)
      * @param {!Array<string>} rows
      * @param {number} offset
-     * @param {?Array<!Defs.IntralineInfo>=} opt_intralineInfos
+     * @param {?Array<!Gerrit.IntralineInfo>=} opt_intralineInfos
      * @return {!Array<!Object>} (GrDiffLine)
      */
     _linesFromRows(lineType, rows, offset, opt_intralineInfos) {
@@ -464,8 +426,8 @@
      * into 2 chunks, one max sized one and the rest (for reasons that are
      * unclear to me).
      *
-     * @param {!Array<!Defs.Chunk>} chunks Chunks as returned from the server
-     * @return {!Array<!Defs.Chunk>} Finer grained chunks.
+     * @param {!Array<!Gerrit.DiffChunk>} chunks Chunks as returned from the server
+     * @return {!Array<!Gerrit.DiffChunk>} Finer grained chunks.
      */
     _splitLargeChunks(chunks) {
       const newChunks = [];
@@ -592,7 +554,7 @@
      * for rendering.
      *
      * @param {!Array<string>} rows
-     * @param {!Array<!Defs.IntralineInfo>} intralineInfos
+     * @param {!Array<!Gerrit.IntralineInfo>} intralineInfos
      * @return {!Array<!Object>} (GrDiffLine.Highlight)
      */
     _convertIntralineInfos(rows, intralineInfos) {
@@ -641,7 +603,7 @@
      * If a group is an addition or a removal, break it down into smaller groups
      * of that type using the MAX_GROUP_SIZE. If the group is a shared chunk
      * or a delta it is returned as the single element of the result array.
-     * @param {!Defs.Chunk} chunk A raw chunk from a diff response.
+     * @param {!Gerrit.DiffChunk} chunk A raw chunk from a diff response.
      * @return {!Array<!Array<!Object>>}
      */
     _breakdownChunk(chunk) {
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-selection/gr-diff-selection.html b/polygerrit-ui/app/elements/diff/gr-diff-selection/gr-diff-selection.html
index 27821f1..cfa46a0 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-selection/gr-diff-selection.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff-selection/gr-diff-selection.html
@@ -21,31 +21,6 @@
 
 <dom-module id="gr-diff-selection">
   <template>
-    <style include="shared-styles">
-      /** Select and copy for Polymer 1*/
-      .contentWrapper ::content .content,
-      .contentWrapper ::content .contextControl,
-      .contentWrapper ::content .blame {
-        -webkit-user-select: none;
-        -moz-user-select: none;
-        -ms-user-select: none;
-        user-select: none;
-      }
-
-      :host-context(.selected-left:not(.selected-comment)) .contentWrapper ::content .side-by-side .left + .content .contentText,
-      :host-context(.selected-right:not(.selected-comment)) .contentWrapper ::content .side-by-side .right + .content .contentText,
-      :host-context(.selected-left:not(.selected-comment)) .contentWrapper ::content .unified .left.lineNum ~ .content:not(.both) .contentText,
-      :host-context(.selected-right:not(.selected-comment)) .contentWrapper ::content .unified .right.lineNum ~ .content .contentText,
-      :host-context(.selected-left.selected-comment) .contentWrapper ::content .side-by-side .left + .content .message,
-      :host-context(.selected-right.selected-comment) .contentWrapper ::content .side-by-side .right + .content .message :not(.collapsedContent),
-      :host-context(.selected-comment) .contentWrapper ::content .unified .message :not(.collapsedContent),
-      :host-context(.selected-blame) .contentWrapper ::content .blame {
-        -webkit-user-select: text;
-        -moz-user-select: text;
-        -ms-user-select: text;
-        user-select: text;
-      }
-    </style>
     <div class="contentWrapper">
       <slot></slot>
     </div>
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 d710a85..055b200 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
@@ -73,8 +73,6 @@
     },
 
     _handleDownOnRangeComment(node) {
-      // Keep the original behavior in polymer 1
-      if (!window.POLYMER2) return false;
       if (node &&
           node.nodeName &&
           node.nodeName.toLowerCase() === 'gr-comment-thread') {
@@ -183,13 +181,10 @@
      * For Polymer 2, use shadowRoot.getSelection instead.
      */
     _getSelection() {
-      let selection;
-      if (window.POLYMER2) {
-        const diffHost = util.querySelector(document.body, 'gr-diff');
-        selection = diffHost &&
-          diffHost.shadowRoot &&
-          diffHost.shadowRoot.getSelection();
-      }
+      const diffHost = util.querySelector(document.body, 'gr-diff');
+      const selection = diffHost &&
+        diffHost.shadowRoot &&
+        diffHost.shadowRoot.getSelection();
       return selection ? selection: window.getSelection();
     },
 
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 b55e127..54e202d 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.js
@@ -35,26 +35,10 @@
     RIGHT: 'right',
   };
 
-  const Defs = {};
-
-  /**
-   * Special line number which should not be collapsed into a shared region.
-   *
-   * @typedef {{
-   *  number: number,
-   *  leftSide: boolean
-   * }}
-   */
-  Defs.LineOfInterest;
-
   const LARGE_DIFF_THRESHOLD_LINES = 10000;
   const FULL_CONTEXT = -1;
   const LIMITED_CONTEXT = 10;
 
-  /** @typedef {{start_line: number, start_character: number,
-   *             end_line: number, end_character: number}} */
-  Gerrit.Range;
-
   /**
    * Compare two ranges. Either argument may be falsy, but will only return
    * true if both are falsy or if neither are falsy and have the same position
@@ -134,6 +118,14 @@
      * @event render
      */
 
+    /**
+     * Fired for interaction reporting when a diff context is expanded.
+     * Contains an event.detail with numLines about the number of lines that
+     * were expanded.
+     *
+     * @event diff-context-expanded
+     */
+
     properties: {
       changeNum: String,
       noAutoRender: {
@@ -185,7 +177,7 @@
         observer: '_viewModeObserver',
       },
 
-       /** @type ?Defs.LineOfInterest */
+       /** @type ?Gerrit.LineOfInterest */
       lineOfInterest: Object,
 
       loading: {
@@ -436,7 +428,6 @@
         return [];
       }
 
-      // Polymer2: querySelectorAll returns NodeList instead of Array.
       return Array.from(
           Polymer.dom(this.root).querySelectorAll('.diff-row'));
     },
@@ -488,6 +479,9 @@
       const el = Polymer.dom(e).localTarget;
 
       if (el.classList.contains('showContext')) {
+        this.fire('diff-context-expanded', {
+          numLines: e.detail.numLines,
+        });
         this.$.diffBuilder.showContext(e.detail.groups, e.detail.section);
       } else if (el.classList.contains('lineNum')) {
         this.addDraftAtLine(el);
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 8a0b58c..09342e1 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
@@ -26,6 +26,7 @@
 <link rel="import" href="../../../test/common-test-setup.html"/>
 <script src="../../../scripts/util.js"></script>
 
+<link rel="import" href="../../shared/gr-rest-api-interface/gr-rest-api-interface.html">
 <link rel="import" href="../../shared/gr-rest-api-interface/mock-diff-response_test.html">
 <link rel="import" href="gr-diff.html">
 
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 1e952a3..c7c9b9d 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,6 @@
   const RANGE_HIGHLIGHT = 'style-scope gr-diff range';
   const HOVER_HIGHLIGHT = 'style-scope gr-diff rangeHighlight';
 
-  /** @typedef {{side: string, range: Gerrit.Range, hovering: boolean}} */
-  Gerrit.HoveredRange;
-
   Polymer({
     is: 'gr-ranged-comment-layer',
 
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 ca678a5..5aed3e4 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
@@ -169,10 +169,7 @@
         dialog.querySelectorAll('gr-autocomplete')
             .forEach(input => { input.text = ''; });
 
-        // TODO: reveiw binding for input after drop Polymer 1 support
-        // All docs related to Polymer 2 set binding only for iron-input,
-        // and doesn't add binding to input.
-        dialog.querySelectorAll(window.POLYMER2 ? 'iron-input' : 'input')
+        dialog.querySelectorAll('iron-input')
             .forEach(input => { input.bindValue = ''; });
       }
 
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 c609e20..448e090 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
@@ -74,7 +74,6 @@
     },
 
     _getEndpointParams() {
-      // Polymer2: querySelectorAll returns NodeList instead of Array.
       return Array.from(
           Polymer.dom(this).querySelectorAll('gr-endpoint-param'));
     },
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 bacc226..85eed9c 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
@@ -41,7 +41,6 @@
      * @return {!Array<string>}
      */
     _getDisplayedColumns() {
-      // Polymer2: querySelectorAll returns NodeList instead of Array.
       return Array.from(Polymer.dom(this.root)
           .querySelectorAll('.checkboxContainer input:not([name=number])'))
           .filter(checkbox => checkbox.checked)
diff --git a/polygerrit-ui/app/elements/settings/gr-identities/gr-identities.html b/polygerrit-ui/app/elements/settings/gr-identities/gr-identities.html
index 62a3862..ee855cc 100644
--- a/polygerrit-ui/app/elements/settings/gr-identities/gr-identities.html
+++ b/polygerrit-ui/app/elements/settings/gr-identities/gr-identities.html
@@ -84,13 +84,13 @@
           </tbody>
         </table>
       </fieldset>
-      <dom-if if="[[_showLinkAnotherIdentity]]">
+      <template is="dom-if" if="[[_showLinkAnotherIdentity]]">
         <fieldset>
           <a href$="[[_computeLinkAnotherIdentity()]]">
             <gr-button id="linkAnotherIdentity" link>Link Another Identity</gr-button>
           </a>
         </fieldset>
-      </dom-if>
+      </template>
     </div>
     <gr-overlay id="overlay" with-backdrop>
       <gr-confirm-delete-item-dialog
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 6d364c9..5f1d41a 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,24 +19,6 @@
 
   const VALID_EMAIL_ALERT = 'Please input a valid email.';
 
-  const Defs = {};
-
-  /**
-   * @typedef {{
-   *   name: string,
-   *   value: Object,
-   * }}
-   */
-  Defs.GrSuggestionItem;
-
-  /**
-   * @typedef {{
-   *    getSuggestions: function(string): Promise<Array<Object>>,
-   *    makeSuggestionItem: function(Object): Defs.GrSuggestionItem,
-   * }}
-   */
-  Defs.GrSuggestionsProvider;
-
   Polymer({
     is: 'gr-account-list',
 
@@ -62,7 +44,7 @@
 
       /**
        * Returns suggestions and convert them to list item
-       * @type {Defs.GrSuggestionsProvider}
+       * @type {Gerrit.GrSuggestionsProvider}
        */
       suggestionsProvider: {
         type: Object,
@@ -99,7 +81,7 @@
       },
 
       /** Returns suggestion items
-      * @type {!function(string): Promise<Array<Defs.GrSuggestionItem>>}
+      * @type {!function(string): Promise<Array<Gerrit.GrSuggestionItem>>}
       */
       _querySuggestions: {
         type: Function,
@@ -119,7 +101,6 @@
     },
 
     get accountChips() {
-      // Polymer2: querySelectorAll returns NodeList instead of Array.
       return Array.from(
           Polymer.dom(this.root).querySelectorAll('gr-account-chip'));
     },
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 9287e27..b8c76ff 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
@@ -161,7 +161,6 @@
       if (this.suggestions.length > 0) {
         if (!this.isHidden) {
           Polymer.dom.flush();
-          // Polymer2: querySelectorAll returns NodeList instead of Array.
           this._suggestionEls = Array.from(
               this.$.suggestions.querySelectorAll('li'));
           this._resetCursorIndex();
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 093667c..b7fe8b0 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
@@ -212,7 +212,8 @@
     },
 
     _hideActions(_showActions, _lastComment) {
-      return !_showActions || !_lastComment || !!_lastComment.__draft;
+      return !_showActions || !_lastComment || !!_lastComment.__draft ||
+        !!_lastComment.robot_id;
     },
 
     _getLastComment() {
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 86da001..b6222b4 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
@@ -161,6 +161,9 @@
       showActions = true;
       lastComment.__draft = true;
       assert.equal(element._hideActions(showActions, lastComment), true);
+      const robotComment = {};
+      robotComment.robot_id = true;
+      assert.equal(element._hideActions(showActions, robotComment), true);
     });
 
     test('setting project name loads the project config', done => {
diff --git a/polygerrit-ui/app/elements/shared/gr-editable-content/gr-editable-content.html b/polygerrit-ui/app/elements/shared/gr-editable-content/gr-editable-content.html
index 1b8b0d7..7f85082 100644
--- a/polygerrit-ui/app/elements/shared/gr-editable-content/gr-editable-content.html
+++ b/polygerrit-ui/app/elements/shared/gr-editable-content/gr-editable-content.html
@@ -34,14 +34,14 @@
         background-color: var(--view-background-color);
         border: 1px solid var(--border-color);
         border-radius: var(--border-radius);
-        padding: var(--spacing-s);
+        padding: var(--spacing-m);
       }
       .editor iron-autogrow-textarea {
         background-color: var(--view-background-color);
         width: 100%;
 
         --iron-autogrow-textarea: {
-          padding: 4px;
+          padding: var(--spacing-m);
           box-sizing: border-box;
           overflow-y: hidden;
           white-space: pre;
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 1a5356f..4485551 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
@@ -160,11 +160,6 @@
       return this.$.input.$.nativeInput || this.$.input.$.input;
     },
 
-    /**
-     * @suppress {checkTypes}
-     * Closure doesn't think 'e' is an Event.
-     * TODO(beckysiegel) figure out why.
-     */
     _handleEnter(e) {
       e = this.getKeyboardEvent(e);
       const target = Polymer.dom(e).rootTarget;
@@ -174,11 +169,6 @@
       }
     },
 
-    /**
-     * @suppress {checkTypes}
-     * Closure doesn't think 'e' is an Event.
-     * TODO(beckysiegel) figure out why.
-     */
     _handleEsc(e) {
       e = this.getKeyboardEvent(e);
       const target = Polymer.dom(e).rootTarget;
diff --git a/polygerrit-ui/app/elements/shared/gr-icons/gr-icons.html b/polygerrit-ui/app/elements/shared/gr-icons/gr-icons.html
index 7bd6f48..743923b 100644
--- a/polygerrit-ui/app/elements/shared/gr-icons/gr-icons.html
+++ b/polygerrit-ui/app/elements/shared/gr-icons/gr-icons.html
@@ -76,6 +76,7 @@
       <g id="restore"><path d="M12,8 L12,13 L16.28,15.54 L17,14.33 L13.5,12.25 L13.5,8 L12,8 Z M13,3 C8.03,3 4,7.03 4,12 L1,12 L4.89,15.89 L4.96,16.03 L9,12 L6,12 C6,8.13 9.13,5 13,5 C16.87,5 20,8.13 20,12 C20,15.87 16.87,19 13,19 C11.07,19 9.32,18.21 8.06,16.94 L6.64,18.36 C8.27,19.99 10.51,21 13,21 C17.97,21 22,16.97 22,12 C22,7.03 17.97,3 13,3 Z"></path></g>
       <!-- This is a custom PolyGerrit SVG -->
       <g id="revert"><path d="M12.3,8.5 C9.64999995,8.5 7.24999995,9.49 5.39999995,11.1 L1.79999995,7.5 L1.79999995,16.5 L10.8,16.5 L7.17999995,12.88 C8.56999995,11.72 10.34,11 12.3,11 C15.84,11 18.85,13.31 19.9,16.5 L22.27,15.72 C20.88,11.53 16.95,8.5 12.3,8.5"></path></g>
+      <g id="revert_submission"><path d="M12.3,8.5 C9.64999995,8.5 7.24999995,9.49 5.39999995,11.1 L1.79999995,7.5 L1.79999995,16.5 L10.8,16.5 L7.17999995,12.88 C8.56999995,11.72 10.34,11 12.3,11 C15.84,11 18.85,13.31 19.9,16.5 L22.27,15.72 C20.88,11.53 16.95,8.5 12.3,8.5"></path></g>
       <!-- This is a custom PolyGerrit SVG -->
       <g id="stopEdit"><path d="M4 4 20 4 20 20 4 20z"></path></g>
       <!-- This is a custom PolyGerrit SVG -->
diff --git a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-annotation-actions-js-api.js b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-annotation-actions-js-api.js
index 349e441..98268c5 100644
--- a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-annotation-actions-js-api.js
+++ b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-annotation-actions-js-api.js
@@ -65,10 +65,8 @@
    * providers are not supported. A second call will just overwrite the
    * provider of the first call.
    *
-   * TODO(brohlfs): Replace Array<Object> type by Array<Gerrit.CoverageRange>.
-   *
    * @param {function(changeNum, path, basePatchNum, patchNum):
-   * !Promise<!Array<Object>>} coverageProvider
+   * !Promise<!Array<!Gerrit.CoverageRange>>} coverageProvider
    * @return {GrAnnotationActionsInterface}
    */
   GrAnnotationActionsInterface.prototype.setCoverageProvider = function(
diff --git a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-api-utils.js b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-api-utils.js
index 3db7c63..4123f70 100644
--- a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-api-utils.js
+++ b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-api-utils.js
@@ -68,7 +68,7 @@
     return pathname.split('/')[2].split('.')[0];
   }
 
-  // TODO (taoalpha): to be deprecated.
+  // TODO(taoalpha): to be deprecated.
   function send(method, url, opt_callback, opt_payload) {
     return getRestAPI().send(method, url, opt_payload).then(response => {
       if (response.status < 200 || response.status >= 300) {
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 1bbb832..dd18987 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
@@ -25,6 +25,7 @@
     COMMIT_MSG_EDIT: 'commitmsgedit',
     COMMENT: 'comment',
     REVERT: 'revert',
+    REVERT_SUBMISSION: 'revert_submission',
     POST_REVERT: 'postrevert',
     ANNOTATE_DIFF: 'annotatediff',
     ADMIN_MENU_LINKS: 'admin-menu-links',
@@ -213,6 +214,17 @@
       return revertMsg;
     },
 
+    modifyRevertSubmissionMsg(change, revertSubmissionMsg, origMsg) {
+      for (const cb of this._getEventCallbacks(EventType.REVERT_SUBMISSION)) {
+        try {
+          revertSubmissionMsg = cb(change, revertSubmissionMsg, origMsg);
+        } catch (err) {
+          console.error(err);
+        }
+      }
+      return revertSubmissionMsg;
+    },
+
     getDiffLayers(path, changeNum, patchNum) {
       const layers = [];
       for (const annotationApi of
@@ -234,13 +246,11 @@
      * provider, the first one is used. If no plugin offers a coverage provider,
      * will resolve to [].
      *
-     * TODO(brohlfs): Replace Array<Object> type by Array<Gerrit.CoverageRange>.
-     *
      * @param {string|number} changeNum
      * @param {string} path
      * @param {string|number} basePatchNum
      * @param {string|number} patchNum
-     * @return {!Promise<!Array<Object>>}
+     * @return {!Promise<!Array<!Gerrit.CoverageRange>>}
      */
     getCoverageRanges(changeNum, path, basePatchNum, patchNum) {
       return Gerrit.awaitPluginsLoaded().then(() => {
diff --git a/polygerrit-ui/app/elements/shared/gr-linked-text/link-text-parser.js b/polygerrit-ui/app/elements/shared/gr-linked-text/link-text-parser.js
index b87aeef..61e28cc 100644
--- a/polygerrit-ui/app/elements/shared/gr-linked-text/link-text-parser.js
+++ b/polygerrit-ui/app/elements/shared/gr-linked-text/link-text-parser.js
@@ -17,17 +17,6 @@
 (function() {
   'use strict';
 
-  const Defs = {};
-
-  /**
-   * @typedef {{
-   *    html: Node,
-   *    position: number,
-   *    length: number,
-   * }}
-   */
-  Defs.CommentLinkItem;
-
   /**
    * Pattern describing URLs with supported protocols.
    * @type {RegExp}
@@ -69,7 +58,7 @@
    * generated by the commentlinks config, emit parsing callbacks.
    * @param {string} text The chuml of source text over which the outputArray
    *     items range.
-   * @param {!Array<Defs.CommentLinkItem>} outputArray The list of items to add
+   * @param {!Array<Gerrit.CommentLinkItem>} outputArray The list of items to add
    *     resulting from commentlink matches.
    */
   GrLinkTextParser.prototype.processLinks = function(text, outputArray) {
@@ -104,7 +93,7 @@
   /**
    * Sort the given array of CommentLinkItems such that the positions are in
    * reverse order.
-   * @param {!Array<Defs.CommentLinkItem>} outputArray
+   * @param {!Array<Gerrit.CommentLinkItem>} outputArray
    */
   GrLinkTextParser.prototype.sortArrayReverse = function(outputArray) {
     outputArray.sort((a, b) => b.position - a.position);
@@ -126,7 +115,7 @@
    *     starts.
    * @param {number} length The number of characters in the source text
    *     represented by the item.
-   * @param {!Array<Defs.CommentLinkItem>} outputArray The array to which the
+   * @param {!Array<Gerrit.CommentLinkItem>} outputArray The array to which the
    *     new item is to be appended.
    */
   GrLinkTextParser.prototype.addItem =
@@ -167,7 +156,7 @@
    *     starts.
    * @param {number} length The number of characters in the source text
    *     represented by the link.
-   * @param {!Array<Defs.CommentLinkItem>} outputArray The array to which the
+   * @param {!Array<Gerrit.CommentLinkItem>} outputArray The array to which the
    *     new item is to be appended.
    */
   GrLinkTextParser.prototype.addLink =
@@ -188,7 +177,7 @@
    *     starts.
    * @param {number} length The number of characters in the source text
    *     represented by the item.
-   * @param {!Array<Defs.CommentLinkItem>} outputArray The array to which the
+   * @param {!Array<Gerrit.CommentLinkItem>} outputArray The array to which the
    *     new item is to be appended.
    */
   GrLinkTextParser.prototype.addHTML =
@@ -205,7 +194,7 @@
    * Does the given range overlap with anything already in the item list.
    * @param {number} position
    * @param {number} length
-   * @param {!Array<Defs.CommentLinkItem>} outputArray
+   * @param {!Array<Gerrit.CommentLinkItem>} outputArray
    */
   GrLinkTextParser.prototype.hasOverlap =
       function(position, length, outputArray) {
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 80462d3..6233a33 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
@@ -17,74 +17,6 @@
 (function() {
   'use strict';
 
-  const Defs = {};
-
-  /**
-   * @typedef {{
-   *    basePatchNum: (string|number),
-   *    patchNum: (number),
-   * }}
-   */
-  Defs.patchRange;
-
-  /**
-   * @typedef {{
-   *   changeNum: (string|number),
-   *   endpoint: string,
-   *   patchNum: (string|number|null|undefined),
-   *   errFn: (function(?Response, string=)|null|undefined),
-   *   params: (Object|null|undefined),
-   *   fetchOptions: (Object|null|undefined),
-   *   anonymizedEndpoint: (string|undefined),
-   *   reportEndpointAsIs: (boolean|undefined),
-   * }}
-   */
-  Defs.ChangeFetchRequest;
-
-  /**
-   * Object to describe a request for passing into _send.
-   * - method is the HTTP method to use in the request.
-   * - url is the URL for the request
-   * - body is a request payload.
-   *     TODO (beckysiegel) remove need for number at least.
-   * - errFn is a function to invoke when the request fails.
-   * - cancelCondition is a function that, if provided and returns true, will
-   *   cancel the response after it resolves.
-   * - contentType is the content type of the body.
-   * - headers is a key-value hash to describe HTTP headers for the request.
-   * - parseResponse states whether the result should be parsed as a JSON
-   *     object using getResponseObject.
-   * @typedef {{
-   *   method: string,
-   *   url: string,
-   *   body: (string|number|Object|null|undefined),
-   *   errFn: (function(?Response, string=)|null|undefined),
-   *   contentType: (string|null|undefined),
-   *   headers: (Object|undefined),
-   *   parseResponse: (boolean|undefined),
-   *   anonymizedUrl: (string|undefined),
-   *   reportUrlAsIs: (boolean|undefined),
-   * }}
-   */
-  Defs.SendRequest;
-
-  /**
-   * @typedef {{
-   *   changeNum: (string|number),
-   *   method: string,
-   *   patchNum: (string|number|undefined),
-   *   endpoint: string,
-   *   body: (string|number|Object|null|undefined),
-   *   errFn: (function(?Response, string=)|null|undefined),
-   *   contentType: (string|null|undefined),
-   *   headers: (Object|undefined),
-   *   parseResponse: (boolean|undefined),
-   *   anonymizedEndpoint: (string|undefined),
-   *   reportEndpointAsIs: (boolean|undefined),
-   * }}
-   */
-  Defs.ChangeSendRequest;
-
   const DiffViewMode = {
     SIDE_BY_SIDE: 'SIDE_BY_SIDE',
     UNIFIED: 'UNIFIED_DIFF',
@@ -1194,7 +1126,7 @@
 
     /**
      * @param {number|string} changeNum
-     * @param {Defs.patchRange} patchRange
+     * @param {Gerrit.PatchRange} patchRange
      * @param {number=} opt_parentIndex
      */
     getChangeFiles(changeNum, patchRange, opt_parentIndex) {
@@ -1215,7 +1147,7 @@
 
     /**
      * @param {number|string} changeNum
-     * @param {Defs.patchRange} patchRange
+     * @param {Gerrit.PatchRange} patchRange
      */
     getChangeEditFiles(changeNum, patchRange) {
       let endpoint = '/edit?list';
@@ -1248,7 +1180,7 @@
 
     /**
      * @param {number|string} changeNum
-     * @param {Defs.patchRange} patchRange
+     * @param {Gerrit.PatchRange} patchRange
      * @return {!Promise<!Array<!Object>>}
      */
     getChangeOrEditFiles(changeNum, patchRange) {
@@ -2652,7 +2584,7 @@
     /**
      * Alias for _changeBaseURL.then(send).
      * @todo(beckysiegel) clean up comments
-     * @param {Defs.ChangeSendRequest} req
+     * @param {Gerrit.ChangeSendRequest} req
      * @return {!Promise<!Object>}
      */
     _getChangeURLAndSend(req) {
@@ -2678,7 +2610,7 @@
 
     /**
      * Alias for _changeBaseURL.then(_fetchJSON).
-     * @param {Defs.ChangeFetchRequest} req
+     * @param {Gerrit.ChangeFetchRequest} req
      * @return {!Promise<!Object>}
      */
     _getChangeURLAndFetch(req) {
diff --git a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-apis/gr-rest-api-helper.js b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-apis/gr-rest-api-helper.js
index d42abc3..5cea96b 100644
--- a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-apis/gr-rest-api-helper.js
+++ b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-apis/gr-rest-api-helper.js
@@ -17,36 +17,6 @@
 (function(window) {
   'use strict';
 
-  const Defs = {};
-
-  /**
-   * @typedef {{
-   *    url: string,
-   *    fetchOptions: (Object|null|undefined),
-   *    anonymizedUrl: (string|undefined),
-   * }}
-   */
-  Defs.FetchRequest;
-
-  /**
-   * Object to describe a request for passing into fetchJSON or fetchRawJSON.
-   * - url is the URL for the request (excluding get params)
-   * - errFn is a function to invoke when the request fails.
-   * - cancelCondition is a function that, if provided and returns true, will
-   *     cancel the response after it resolves.
-   * - params is a key-value hash to specify get params for the request URL.
-   * @typedef {{
-   *    url: string,
-   *    errFn: (function(?Response, string=)|null|undefined),
-   *    cancelCondition: (function()|null|undefined),
-   *    params: (Object|null|undefined),
-   *    fetchOptions: (Object|null|undefined),
-   *    anonymizedUrl: (string|undefined),
-   *    reportUrlAsIs: (boolean|undefined),
-   * }}
-   */
-  Defs.FetchJSONRequest;
-
   const JSON_PREFIX = ')]}\'';
   const FAILED_TO_FETCH_ERROR = 'Failed to fetch';
 
@@ -152,7 +122,7 @@
     /**
      * Wraps calls to the underlying authenticated fetch function (_auth.fetch)
      * with timing and logging.
-     * @param {Defs.FetchRequest} req
+     * @param {Gerrit.FetchRequest} req
      */
     fetch(req) {
       const start = Date.now();
@@ -169,7 +139,7 @@
      * Log information about a REST call. Because the elapsed time is determined
      * by this method, it should be called immediately after the request
      * finishes.
-     * @param {Defs.FetchRequest} req
+     * @param {Gerrit.FetchRequest} req
      * @param {number} startTime the time that the request was started.
      * @param {number} status the HTTP status of the response. The status value
      *     is used here rather than the response object so there is no way this
@@ -201,7 +171,7 @@
      * Returns a Promise that resolves to a native Response.
      * Doesn't do error checking. Supports cancel condition. Performs auth.
      * Validates auth expiry errors.
-     * @param {Defs.FetchJSONRequest} req
+     * @param {Gerrit.FetchJSONRequest} req
      */
     fetchRawJSON(req) {
       const urlWithParams = this.urlWithParams(req.url, req.params);
@@ -235,7 +205,7 @@
      * Fetch JSON from url provided.
      * Returns a Promise that resolves to a parsed response.
      * Same as {@link fetchRawJSON}, plus error handling.
-     * @param {Defs.FetchJSONRequest} req
+     * @param {Gerrit.FetchJSONRequest} req
      */
     fetchJSON(req) {
       req = this.addAcceptJsonHeader(req);
@@ -311,8 +281,8 @@
     }
 
     /**
-     * @param {Defs.FetchJSONRequest} req
-     * @return {Defs.FetchJSONRequest}
+     * @param {Gerrit.FetchJSONRequest} req
+     * @return {Gerrit.FetchJSONRequest}
      */
     addAcceptJsonHeader(req) {
       if (!req.fetchOptions) req.fetchOptions = {};
@@ -332,7 +302,7 @@
     }
 
     /**
-     * @param {Defs.FetchJSONRequest} req
+     * @param {Gerrit.FetchJSONRequest} req
      */
     fetchCacheURL(req) {
       if (this._fetchPromisesCache.has(req.url)) {
@@ -359,7 +329,7 @@
 
     /**
      * Send an XHR.
-     * @param {Defs.SendRequest} req
+     * @param {Gerrit.SendRequest} req
      * @return {Promise}
      */
     send(req) {
diff --git a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-reviewer-updates-parser.js b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-reviewer-updates-parser.js
index 2451981..e14b955 100644
--- a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-reviewer-updates-parser.js
+++ b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-reviewer-updates-parser.js
@@ -21,7 +21,6 @@
   if (window.GrReviewerUpdatesParser) { return; }
 
   function GrReviewerUpdatesParser(change) {
-    // TODO (viktard): Polyfill Object.assign for IE.
     this.result = Object.assign({}, change);
     this._lastState = {};
   }
diff --git a/polygerrit-ui/app/template_test_srcs/template_test.js b/polygerrit-ui/app/template_test_srcs/template_test.js
index ec3b7d5..f7d9c43 100644
--- a/polygerrit-ui/app/template_test_srcs/template_test.js
+++ b/polygerrit-ui/app/template_test_srcs/template_test.js
@@ -5,9 +5,6 @@
  * 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.
- *
- * @todo (beckysiegel) Gerrit's class definitions should be recognized in
- *    closure types.
  */
 const EXTERN_NAMES = [
   'Gerrit',
@@ -98,6 +95,13 @@
         EXTERN_NAMES.map( name => { return `var ${name};`; }).join(' '),
   });
 
+  /** Types in Gerrit */
+  additionalSources.push({
+    path: './polygerrit-ui/app/types/types.js',
+    src: fs.readFileSync(
+        `./polygerrit-ui/app/types/types.js`, 'utf-8'),
+  });
+
   const toCheck = [];
   for (key of Object.keys(mappings)) {
     if (mappings[key].html && mappings[key].js) {
diff --git a/polygerrit-ui/app/test/common-test-setup.html b/polygerrit-ui/app/test/common-test-setup.html
index 17dc05e..c1d8bbd 100644
--- a/polygerrit-ui/app/test/common-test-setup.html
+++ b/polygerrit-ui/app/test/common-test-setup.html
@@ -20,7 +20,6 @@
     href="/bower_components/polymer-resin/standalone/polymer-resin.html" />
 <link rel="import" href="../behaviors/safe-types-behavior/safe-types-behavior.html">
 <script>
-  window.POLYMER2 = true;
   security.polymer_resin.install({
     allowedIdentifierPrefixes: [''],
     reportHandler(isViolation, fmt, ...args) {
diff --git a/polygerrit-ui/app/test/common-test-setup.js b/polygerrit-ui/app/test/common-test-setup.js
index 52eb3a3..7ceff7e 100644
--- a/polygerrit-ui/app/test/common-test-setup.js
+++ b/polygerrit-ui/app/test/common-test-setup.js
@@ -15,8 +15,6 @@
  * limitations under the License.
  */
 
-window.POLYMER2 = true;
-
 /**
  * Helps looking up the proper iron-input element during the Polymer 2
  * transition. Polymer 2 uses the <iron-input> element, while Polymer 1 uses
diff --git a/polygerrit-ui/app/types/types.js b/polygerrit-ui/app/types/types.js
new file mode 100644
index 0000000..8dd65cb
--- /dev/null
+++ b/polygerrit-ui/app/types/types.js
@@ -0,0 +1,276 @@
+/**
+ * @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.
+ */
+
+// Type definitions used across multiple files in Gerrit
+
+window.Gerrit = window.Gerrit || {};
+
+/** @enum {string} */
+Gerrit.CoverageType = {
+  /**
+   * start_character and end_character of the range will be ignored for this
+   * type.
+   */
+  COVERED: 'COVERED',
+  /**
+   * start_character and end_character of the range will be ignored for this
+   * type.
+   */
+  NOT_COVERED: 'NOT_COVERED',
+  PARTIALLY_COVERED: 'PARTIALLY_COVERED',
+  /**
+   * You don't have to use this. If there is no coverage information for a
+   * range, then it implicitly means NOT_INSTRUMENTED. start_character and
+   * end_character of the range will be ignored for this type.
+   */
+  NOT_INSTRUMENTED: 'NOT_INSTRUMENTED',
+};
+
+/**
+ * @typedef {{
+ *   start_line: number,
+ *   start_character: number,
+ *   end_line: number,
+ *   end_character: number,
+ * }}
+ */
+Gerrit.Range;
+
+/**
+ * @typedef {{side: string, range: Gerrit.Range, hovering: boolean}}
+ */
+Gerrit.HoveredRange;
+
+/**
+ * @typedef {{
+ *   side: string,
+ *   type: Gerrit.CoverageType,
+ *   code_range: Gerrit.Range,
+ * }}
+ */
+Gerrit.CoverageRange;
+
+/**
+ * @typedef {{
+ *    basePatchNum: (string|number),
+ *    patchNum: (number),
+ * }}
+ */
+Gerrit.PatchRange;
+
+/**
+ * @typedef {{
+ *   changeNum: (string|number),
+ *   endpoint: string,
+ *   patchNum: (string|number|null|undefined),
+ *   errFn: (function(?Response, string=)|null|undefined),
+ *   params: (Object|null|undefined),
+ *   fetchOptions: (Object|null|undefined),
+ *   anonymizedEndpoint: (string|undefined),
+ *   reportEndpointAsIs: (boolean|undefined),
+ * }}
+ */
+Gerrit.ChangeFetchRequest;
+
+/**
+ * Object to describe a request for passing into _send.
+ * - method is the HTTP method to use in the request.
+ * - url is the URL for the request
+ * - body is a request payload.
+ *     TODO (beckysiegel) remove need for number at least.
+ * - errFn is a function to invoke when the request fails.
+ * - cancelCondition is a function that, if provided and returns true, will
+ *   cancel the response after it resolves.
+ * - contentType is the content type of the body.
+ * - headers is a key-value hash to describe HTTP headers for the request.
+ * - parseResponse states whether the result should be parsed as a JSON
+ *     object using getResponseObject.
+ * @typedef {{
+ *   method: string,
+ *   url: string,
+ *   body: (string|number|Object|null|undefined),
+ *   errFn: (function(?Response, string=)|null|undefined),
+ *   contentType: (string|null|undefined),
+ *   headers: (Object|undefined),
+ *   parseResponse: (boolean|undefined),
+ *   anonymizedUrl: (string|undefined),
+ *   reportUrlAsIs: (boolean|undefined),
+ * }}
+ */
+Gerrit.SendRequest;
+
+/**
+ * @typedef {{
+ *   changeNum: (string|number),
+ *   method: string,
+ *   patchNum: (string|number|undefined),
+ *   endpoint: string,
+ *   body: (string|number|Object|null|undefined),
+ *   errFn: (function(?Response, string=)|null|undefined),
+ *   contentType: (string|null|undefined),
+ *   headers: (Object|undefined),
+ *   parseResponse: (boolean|undefined),
+ *   anonymizedEndpoint: (string|undefined),
+ *   reportEndpointAsIs: (boolean|undefined),
+ * }}
+ */
+Gerrit.ChangeSendRequest;
+
+/**
+ * @typedef {{
+ *    url: string,
+ *    fetchOptions: (Object|null|undefined),
+ *    anonymizedUrl: (string|undefined),
+ * }}
+ */
+Gerrit.FetchRequest;
+
+/**
+ * Object to describe a request for passing into fetchJSON or fetchRawJSON.
+ * - url is the URL for the request (excluding get params)
+ * - errFn is a function to invoke when the request fails.
+ * - cancelCondition is a function that, if provided and returns true, will
+ *     cancel the response after it resolves.
+ * - params is a key-value hash to specify get params for the request URL.
+ * @typedef {{
+ *    url: string,
+ *    errFn: (function(?Response, string=)|null|undefined),
+ *    cancelCondition: (function()|null|undefined),
+ *    params: (Object|null|undefined),
+ *    fetchOptions: (Object|null|undefined),
+ *    anonymizedUrl: (string|undefined),
+ *    reportUrlAsIs: (boolean|undefined),
+ * }}
+ */
+Gerrit.FetchJSONRequest;
+
+/**
+ * @typedef {{
+ *    message: string,
+ *    icon: string,
+ *    class: string,
+ *  }}
+ */
+Gerrit.PushCertificateValidation;
+
+/**
+ * Object containing layout values to be used in rendering size-bars.
+ * `max{Inserted,Deleted}` represent the largest values of the
+ * `lines_inserted` and `lines_deleted` fields of the files respectively. The
+ * `max{Addition,Deletion}Width` represent the width of the graphic allocated
+ * to the insertion or deletion side respectively. Finally, the
+ * `deletionOffset` value represents the x-position for the deletion bar.
+ *
+ * @typedef {{
+ *    maxInserted: number,
+ *    maxDeleted: number,
+ *    maxAdditionWidth: number,
+ *    maxDeletionWidth: number,
+ *    deletionOffset: number,
+ * }}
+ */
+Gerrit.LayoutStats;
+
+/**
+ * @typedef {{
+ *    changeNum: number,
+ *    path: string,
+ *    patchRange: !Gerrit.PatchRange,
+ *    projectConfig: (Object|undefined),
+ * }}
+ */
+Gerrit.CommentMeta;
+
+/**
+ * @typedef {{
+ *    meta: !Gerrit.CommentMeta,
+ *    left: !Array,
+ *    right: !Array,
+ * }}
+ */
+Gerrit.CommentsBySide;
+
+/**
+ * The DiffIntralineInfo entity contains information about intraline edits in a
+ * file.
+ *
+ * The information consists of a list of <skip length, mark length> pairs, where
+ * the skip length is the number of characters between the end of the previous
+ * edit and the start of this edit, and the mark length is the number of edited
+ * characters following the skip. The start of the edits is from the beginning
+ * of the related diff content lines.
+ *
+ * Note that the implied newline character at the end of each line is included
+ * in the length calculation, and thus it is possible for the edits to span
+ * newlines.
+ * @typedef {!Array<number>}
+ */
+Gerrit.IntralineInfo;
+
+/**
+ * A portion of the diff that is treated the same.
+ *
+ * Called `DiffContent` in the API, see
+ * https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#diff-content
+ *
+ * @typedef {{
+ *  ab: ?Array<!string>,
+ *  a: ?Array<!string>,
+ *  b: ?Array<!string>,
+ *  skip: ?number,
+ *  edit_a: ?Array<!Gerrit.IntralineInfo>,
+ *  edit_b: ?Array<!Gerrit.IntralineInfo>,
+ *  due_to_rebase: ?boolean,
+ *  common: ?boolean
+ * }}
+ */
+Gerrit.DiffChunk;
+
+/**
+ * Special line number which should not be collapsed into a shared region.
+ *
+ * @typedef {{
+ *  number: number,
+ *  leftSide: boolean
+ * }}
+ */
+Gerrit.LineOfInterest;
+
+/**
+ * @typedef {{
+ *    html: Node,
+ *    position: number,
+ *    length: number,
+ * }}
+ */
+Gerrit.CommentLinkItem;
+
+/**
+ * @typedef {{
+ *   name: string,
+ *   value: Object,
+ * }}
+ */
+Gerrit.GrSuggestionItem;
+
+/**
+ * @typedef {{
+ *    getSuggestions: function(string): Promise<Array<Object>>,
+ *    makeSuggestionItem: function(Object): Gerrit.GrSuggestionItem,
+ * }}
+ */
+Gerrit.GrSuggestionsProvider;
\ No newline at end of file
diff --git a/tools/workspace-status.cmd b/tools/workspace-status.cmd
deleted file mode 100644
index bc1560d..0000000
--- a/tools/workspace-status.cmd
+++ /dev/null
@@ -1 +0,0 @@
-echo STABLE_BUILD_GERRIT_LABEL dev
diff --git a/tools/workspace-status.sh b/tools/workspace-status.sh
deleted file mode 100755
index 2b1a4ba..0000000
--- a/tools/workspace-status.sh
+++ /dev/null
@@ -1,21 +0,0 @@
-#!/usr/bin/env bash
-
-# This script will be run by bazel when the build process starts to
-# generate key-value information that represents the status of the
-# workspace. The output should be like
-#
-# KEY1 VALUE1
-# KEY2 VALUE2
-#
-# If the script exits with non-zero code, it's considered as a failure
-# and the output will be discarded.
-
-function rev() {
-  cd $1; git describe --always --match "v[0-9].*" --dirty
-}
-
-echo STABLE_BUILD_GERRIT_LABEL $(rev .)
-for p in plugins/* ; do
-  test -d "$p" || continue
-  echo STABLE_BUILD_$(echo $(basename $p)_LABEL|tr '[a-z]' '[A-Z]' ) $(rev $p || echo unknown)
-done
diff --git a/tools/workspace_status.py b/tools/workspace_status.py
new file mode 100644
index 0000000..86df519
--- /dev/null
+++ b/tools/workspace_status.py
@@ -0,0 +1,44 @@
+#!/usr/bin/env python
+
+# This script will be run by bazel when the build process starts to
+# generate key-value information that represents the status of the
+# workspace. The output should be like
+#
+# KEY1 VALUE1
+# KEY2 VALUE2
+#
+# If the script exits with non-zero code, it's considered as a failure
+# and the output will be discarded.
+
+from __future__ import print_function
+import os
+import subprocess
+import sys
+
+ROOT = os.path.abspath(__file__)
+while not os.path.exists(os.path.join(ROOT, 'WORKSPACE')):
+    ROOT = os.path.dirname(ROOT)
+CMD = ['git', 'describe', '--always', '--match', 'v[0-9].*', '--dirty']
+
+
+def revision(directory, parent):
+    try:
+        os.chdir(directory)
+        return subprocess.check_output(CMD).strip().decode("utf-8")
+    except OSError as err:
+        print('could not invoke git: %s' % err, file=sys.stderr)
+        sys.exit(1)
+    except subprocess.CalledProcessError as err:
+        # ignore "not a git repository error" to report unknown version
+        return None
+    finally:
+        os.chdir(parent)
+
+
+print("STABLE_BUILD_GERRIT_LABEL %s" % revision(ROOT, ROOT))
+for d in os.listdir(os.path.join(ROOT, 'plugins')):
+    p = os.path.join('plugins', d)
+    if os.path.isdir(p):
+        v = revision(p, ROOT)
+        print('STABLE_BUILD_%s_LABEL %s' % (os.path.basename(p).upper(),
+                                            v if v else 'unknown'))