Merge "Documentation: clarify what happens if you override a label in a child."
diff --git a/polygerrit-ui/app/elements/admin/gr-access-section/gr-access-section.html b/polygerrit-ui/app/elements/admin/gr-access-section/gr-access-section.html
index cf1102d..3779402 100644
--- a/polygerrit-ui/app/elements/admin/gr-access-section/gr-access-section.html
+++ b/polygerrit-ui/app/elements/admin/gr-access-section/gr-access-section.html
@@ -122,7 +122,8 @@
                 labels="[[labels]]"
                 section="[[section.id]]"
                 editing="[[editing]]"
-                groups="[[groups]]">
+                groups="[[groups]]"
+                on-added-permission-removed="_handleAddedPermissionRemoved">
             </gr-permission>
           </template>
           <div id="addPermission">
diff --git a/polygerrit-ui/app/elements/admin/gr-access-section/gr-access-section.js b/polygerrit-ui/app/elements/admin/gr-access-section/gr-access-section.js
index b6d2955..4574e11 100644
--- a/polygerrit-ui/app/elements/admin/gr-access-section/gr-access-section.js
+++ b/polygerrit-ui/app/elements/admin/gr-access-section/gr-access-section.js
@@ -23,6 +23,11 @@
    * @event access-modified
    */
 
+  /**
+   * Fired when a section that was previously added was removed.
+   * @event added-section-removed
+   */
+
   const GLOBAL_NAME = 'GLOBAL_CAPABILITIES';
 
   // The name that gets automatically input when a new reference is added.
@@ -130,6 +135,12 @@
       return section.id === 'GLOBAL_CAPABILITIES' ? 'hide' : '';
     },
 
+    _handleAddedPermissionRemoved(e) {
+      const index = e.model.index;
+      this._permissions = this._permissions.slice(0, index).concat(
+          this._permissions.slice(index + 1, this._permissions.length));
+    },
+
     _computeLabelOptions(labels) {
       const labelOptions = [];
       for (const labelName of Object.keys(labels)) {
@@ -184,6 +195,10 @@
     },
 
     _handleRemoveReference() {
+      if (this.section.value.added) {
+        this.dispatchEvent(new CustomEvent('added-section-removed',
+            {bubbles: true}));
+      }
       this._deleted = true;
       this.section.value.deleted = true;
       this.dispatchEvent(new CustomEvent('access-modified', {bubbles: true}));
diff --git a/polygerrit-ui/app/elements/admin/gr-access-section/gr-access-section_test.html b/polygerrit-ui/app/elements/admin/gr-access-section/gr-access-section_test.html
index ea42101..ad401b8 100644
--- a/polygerrit-ui/app/elements/admin/gr-access-section/gr-access-section_test.html
+++ b/polygerrit-ui/app/elements/admin/gr-access-section/gr-access-section_test.html
@@ -507,6 +507,23 @@
           assert.isFalse(element._deleted);
           assert.isNotOk(element.section.value.deleted);
         });
+
+        test('removing an added permission', () => {
+          element.editing = true;
+          assert.equal(element._permissions.length, 1);
+          element.$$('gr-permission').fire('added-permission-removed');
+          flushAsynchronousOperations();
+          assert.equal(element._permissions.length, 0);
+        });
+
+        test('remove an added section', () => {
+          const removeStub = sandbox.stub();
+          element.addEventListener('added-section-removed', removeStub);
+          element.editing = true;
+          element.section.value.added = true;
+          MockInteractions.tap(element.$.deleteBtn);
+          assert.isTrue(removeStub.called);
+        });
       });
     });
   });
diff --git a/polygerrit-ui/app/elements/admin/gr-admin-view/gr-admin-view.html b/polygerrit-ui/app/elements/admin/gr-admin-view/gr-admin-view.html
index 3a54152c..3c9cdfb 100644
--- a/polygerrit-ui/app/elements/admin/gr-admin-view/gr-admin-view.html
+++ b/polygerrit-ui/app/elements/admin/gr-admin-view/gr-admin-view.html
@@ -24,6 +24,8 @@
 <link rel="import" href="../../../styles/gr-page-nav-styles.html">
 <link rel="import" href="../../../styles/shared-styles.html">
 <link rel="import" href="../../core/gr-navigation/gr-navigation.html">
+<link rel="import" href="../../shared/gr-dropdown-list/gr-dropdown-list.html">
+<link rel="import" href="../../shared/gr-icons/gr-icons.html">
 <link rel="import" href="../../shared/gr-js-api-interface/gr-js-api-interface.html">
 <link rel="import" href="../../shared/gr-page-nav/gr-page-nav.html">
 <link rel="import" href="../../shared/gr-rest-api-interface/gr-rest-api-interface.html">
@@ -43,7 +45,38 @@
   <template>
     <style include="shared-styles"></style>
     <style include="gr-menu-page-styles"></style>
-    <style include="gr-page-nav-styles"></style>
+    <style include="gr-page-nav-styles">
+      gr-dropdown-list {
+        --trigger-style: {
+          text-transform: none;
+        }
+      }
+      .breadcrumbText {
+        /* Same as dropdown trigger so chevron spacing is consistent. */
+        padding: 5px 4px;
+      }
+      iron-icon {
+        margin: 0 .2em;
+      }
+      .breadcrumb {
+        align-items: center;
+        display: flex;
+      }
+      .mainHeader {
+        align-items: baseline;
+        border-bottom: 1px solid var(--border-color);
+        display: flex;
+      }
+      .selectText {
+        display: none;
+      }
+      .selectText.show {
+        display: inline-block;
+      }
+      main.breadcrumbs:not(.table) {
+        margin-top: 1em;
+      }
+    </style>
     <gr-page-nav class="navStyles">
       <ul class="sectionContent">
         <template id="adminNav" is="dom-repeat" items="[[_filteredLinks]]">
@@ -75,29 +108,26 @@
         </template>
       </ul>
     </gr-page-nav>
+    <template is="dom-if" if="[[_subsectionLinks.length]]">
+      <section class="mainHeader">
+        <span class="breadcrumb">
+          <span class="breadcrumbText">[[_breadcrumbParentName]]</span>
+          <iron-icon icon="gr-icons:chevron-right"></iron-icon>
+        </span>
+        <gr-dropdown-list
+            lowercase
+            id="pageSelect"
+            value="[[_computeSelectValue(params)]]"
+            items="[[_subsectionLinks]]"
+            on-value-change="_handleSubsectionChange">
+        </gr-dropdown-list>
+      </section>
+    </template>
     <template is="dom-if" if="[[_showRepoList]]" restamp="true">
       <main class="table">
         <gr-repo-list class="table" params="[[params]]"></gr-repo-list>
       </main>
     </template>
-    <template is="dom-if" if="[[_showRepoMain]]" restamp="true">
-      <main>
-        <gr-repo repo="[[params.repo]]"></gr-repo>
-      </main>
-    </template>
-    <template is="dom-if" if="[[_showGroup]]" restamp="true">
-      <main>
-        <gr-group
-            group-id="[[params.groupId]]"
-            on-name-changed="_updateGroupName"></gr-group>
-      </main>
-    </template>
-    <template is="dom-if" if="[[_showGroupMembers]]" restamp="true">
-      <main>
-        <gr-group-members
-            group-id="[[params.groupId]]"></gr-group-members>
-      </main>
-    </template>
     <template is="dom-if" if="[[_showGroupList]]" restamp="true">
       <main class="table">
         <gr-admin-group-list class="table" params="[[params]]">
@@ -109,35 +139,53 @@
         <gr-plugin-list class="table" params="[[params]]"></gr-plugin-list>
       </main>
     </template>
+    <template is="dom-if" if="[[_showRepoMain]]" restamp="true">
+      <main class="breadcrumbs">
+        <gr-repo repo="[[params.repo]]"></gr-repo>
+      </main>
+    </template>
+    <template is="dom-if" if="[[_showGroup]]" restamp="true">
+      <main class="breadcrumbs">
+        <gr-group
+            group-id="[[params.groupId]]"
+            on-name-changed="_updateGroupName"></gr-group>
+      </main>
+    </template>
+    <template is="dom-if" if="[[_showGroupMembers]]" restamp="true">
+      <main class="breadcrumbs">
+        <gr-group-members
+            group-id="[[params.groupId]]"></gr-group-members>
+      </main>
+    </template>
     <template is="dom-if" if="[[_showRepoDetailList]]" restamp="true">
-      <main class="table">
+      <main class="table breadcrumbs">
         <gr-repo-detail-list
             params="[[params]]"
             class="table"></gr-repo-detail-list>
       </main>
     </template>
     <template is="dom-if" if="[[_showGroupAuditLog]]" restamp="true">
-      <main class="table">
+      <main class="table breadcrumbs">
         <gr-group-audit-log
             group-id="[[params.groupId]]"
             class="table"></gr-group-audit-log>
       </main>
     </template>
     <template is="dom-if" if="[[_showRepoCommands]]" restamp="true">
-      <main>
+      <main class="breadcrumbs">
         <gr-repo-commands
             repo="[[params.repo]]"></gr-repo-commands>
       </main>
     </template>
     <template is="dom-if" if="[[_showRepoAccess]]" restamp="true">
-      <main class="table">
+      <main class="breadcrumbs">
         <gr-repo-access
             path="[[path]]"
             repo="[[params.repo]]"></gr-repo-access>
       </main>
     </template>
     <template is="dom-if" if="[[_showRepoDashboards]]" restamp="true">
-      <main class="table">
+      <main class="table breadcrumbs">
         <gr-repo-dashboards repo="[[params.repo]]"></gr-repo-dashboards>
       </main>
     </template>
diff --git a/polygerrit-ui/app/elements/admin/gr-admin-view/gr-admin-view.js b/polygerrit-ui/app/elements/admin/gr-admin-view/gr-admin-view.js
index f04fafb..3d430c2 100644
--- a/polygerrit-ui/app/elements/admin/gr-admin-view/gr-admin-view.js
+++ b/polygerrit-ui/app/elements/admin/gr-admin-view/gr-admin-view.js
@@ -29,6 +29,7 @@
       path: String,
       adminView: String,
 
+      _breadcrumbParentName: String,
       _repoName: String,
       _groupId: {
         type: Number,
@@ -103,6 +104,8 @@
             options)
             .then(res => {
               this._filteredLinks = res.links;
+              this._breadcrumbParentName = res.expandedSection ?
+                  res.expandedSection.name : '';
 
               if (!res.expandedSection) {
                 this._subsectionLinks = [];
@@ -112,7 +115,7 @@
               .concat(res.expandedSection.children).map(section => {
                 return {
                   text: !section.detailType ? 'Home' : section.name,
-                  value: section.view + section.detailType,
+                  value: section.view + (section.detailType || ''),
                   view: section.view,
                   url: section.url,
                   detailType: section.detailType,
@@ -123,6 +126,17 @@
       });
     },
 
+    _computeSelectValue(params) {
+      if (!params || !params.view) { return; }
+      return params.view + (params.detail || '');
+    },
+
+    _selectedIsCurrentPage(selected) {
+      return (selected.parent === (this._repoName || this._groupId) &&
+          selected.view === this.params.view &&
+          selected.detailType === this.params.detail);
+    },
+
     _handleSubsectionChange(e) {
       const selected = this._subsectionLinks
           .find(section => section.value === e.detail.value);
@@ -165,16 +179,22 @@
       this.set('_showPluginList', isAdminView &&
           params.adminView === 'gr-plugin-list');
 
+      let needsReload = false;
       if (params.repo !== this._repoName) {
         this._repoName = params.repo || '';
         // Reloads the admin menu.
-        this.reload();
+        needsReload = true;
       }
       if (params.groupId !== this._groupId) {
         this._groupId = params.groupId || '';
         // Reloads the admin menu.
-        this.reload();
+        needsReload = true;
       }
+      if (this._breadcrumbParentName && !params.groupId && !params.repo) {
+        needsReload = true;
+      }
+      if (!needsReload) { return; }
+      this.reload();
     },
 
     _computeSelectedTitle(params) {
diff --git a/polygerrit-ui/app/elements/admin/gr-admin-view/gr-admin-view_test.html b/polygerrit-ui/app/elements/admin/gr-admin-view/gr-admin-view_test.html
index facfad5f..678fb99 100644
--- a/polygerrit-ui/app/elements/admin/gr-admin-view/gr-admin-view_test.html
+++ b/polygerrit-ui/app/elements/admin/gr-admin-view/gr-admin-view_test.html
@@ -79,7 +79,6 @@
         name: 'Repositories',
         url: '/admin/repos',
         view: 'gr-repo-list',
-        children: [],
       }];
 
       element.params = {
@@ -189,17 +188,10 @@
       });
       element.reload().then(() => {
         flushAsynchronousOperations();
-        assert.equal(element._filteredLinks.length, 3);
-
-        // Repos
-        assert.equal(element._filteredLinks[0].subsection.children.length, 5);
-        assert.equal(element._filteredLinks[0].subsection.name, 'Test Repo');
-
-        // Groups
-        assert.isNotOk(element._filteredLinks[1].subsection);
-
-        // Plugins
-        assert.isNotOk(element._filteredLinks[2].subsection);
+        assert.equal(Polymer.dom(element.root)
+            .querySelectorAll('.sectionTitle').length, 3);
+        assert.equal(element.$$('.breadcrumbText').innerText, 'Test Repo');
+        assert.equal(element.$$('#pageSelect').items.length, 6);
         done();
       });
     });
@@ -287,6 +279,188 @@
       element.$$('gr-group').fire('name-changed', {name: newName});
     });
 
+    test('dropdown displays if there is a subsection', () => {
+      assert.isNotOk(element.$$('.mainHeader'));
+      element._subsectionLinks = [
+        {
+          text: 'Home',
+          value: 'repo',
+          view: 'repo',
+          url: '',
+          parent: 'my-repo',
+          detailType: undefined,
+        },
+      ];
+      flushAsynchronousOperations();
+      assert.isOk(element.$$('.mainHeader'));
+      element._subsectionLinks = undefined;
+      flushAsynchronousOperations();
+      assert.equal(getComputedStyle(element.$$('.mainHeader')).display, 'none');
+    });
+
+    test('Dropdown only triggers navigation on explicit select', done => {
+      element._repoName = 'my-repo';
+      element.params = {
+        repo: 'my-repo',
+        view: Gerrit.Nav.View.REPO,
+        detail: Gerrit.Nav.RepoDetailView.ACCESS,
+      };
+      sandbox.stub(element.$.restAPI, 'getAccountCapabilities', () => {
+        return Promise.resolve({
+          createGroup: true,
+          createProject: true,
+          viewPlugins: true,
+        });
+      });
+      sandbox.stub(element.$.restAPI, 'getAccount', () => {
+        return Promise.resolve({_id: 1});
+      });
+      flushAsynchronousOperations();
+      const expectedFilteredLinks = [
+        {
+          name: 'Repositories',
+          noBaseUrl: true,
+          url: '/admin/repos',
+          view: 'gr-repo-list',
+          viewableToAll: true,
+          subsection: {
+            name: 'my-repo',
+            view: 'repo',
+            url: '',
+            children: [
+              {
+                name: 'Access',
+                view: 'repo',
+                detailType: 'access',
+                url: '',
+              },
+              {
+                name: 'Commands',
+                view: 'repo',
+                detailType: 'commands',
+                url: '',
+              },
+              {
+                name: 'Branches',
+                view: 'repo',
+                detailType: 'branches',
+                url: '',
+              },
+              {
+                name: 'Tags',
+                view: 'repo',
+                detailType: 'tags',
+                url: '',
+              },
+              {
+                name: 'Dashboards',
+                view: 'repo',
+                detailType: 'dashboards',
+                url: '',
+              },
+            ],
+          },
+        },
+        {
+          name: 'Groups',
+          section: 'Groups',
+          noBaseUrl: true,
+          url: '/admin/groups',
+          view: 'gr-admin-group-list',
+        },
+        {
+          name: 'Plugins',
+          capability: 'viewPlugins',
+          section: 'Plugins',
+          noBaseUrl: true,
+          url: '/admin/plugins',
+          view: 'gr-plugin-list',
+        },
+      ];
+      const expectedSubsectionLinks = [
+        {
+          text: 'Home',
+          value: 'repo',
+          view: 'repo',
+          url: '',
+          parent: 'my-repo',
+          detailType: undefined,
+        },
+        {
+          text: 'Access',
+          value: 'repoaccess',
+          view: 'repo',
+          url: '',
+          detailType: 'access',
+          parent: 'my-repo',
+        },
+        {
+          text: 'Commands',
+          value: 'repocommands',
+          view: 'repo',
+          url: '',
+          detailType: 'commands',
+          parent: 'my-repo',
+        },
+        {
+          text: 'Branches',
+          value: 'repobranches',
+          view: 'repo',
+          url: '',
+          detailType: 'branches',
+          parent: 'my-repo',
+        },
+        {
+          text: 'Tags',
+          value: 'repotags',
+          view: 'repo',
+          url: '',
+          detailType: 'tags',
+          parent: 'my-repo',
+        },
+        {
+          text: 'Dashboards',
+          value: 'repodashboards',
+          view: 'repo',
+          url: '',
+          detailType: 'dashboards',
+          parent: 'my-repo',
+        },
+      ];
+      sandbox.stub(Gerrit.Nav, 'navigateToRelativeUrl');
+      sandbox.spy(element, '_selectedIsCurrentPage');
+      sandbox.spy(element, '_handleSubsectionChange');
+      element.reload().then(() => {
+        assert.deepEqual(element._filteredLinks, expectedFilteredLinks);
+        assert.deepEqual(element._subsectionLinks, expectedSubsectionLinks);
+        assert.equal(element.$$('#pageSelect').value, 'repoaccess');
+        assert.isTrue(element._selectedIsCurrentPage.calledOnce);
+        // Doesn't trigger navigation from the page select menu.
+        assert.isFalse(Gerrit.Nav.navigateToRelativeUrl.called);
+
+        // When explicitly changed, navigation is called
+        element.$$('#pageSelect').value = 'repo';
+        assert.isTrue(element._selectedIsCurrentPage.calledTwice);
+        assert.isTrue(Gerrit.Nav.navigateToRelativeUrl.calledOnce);
+        done();
+      });
+    });
+
+    test('_selectedIsCurrentPage', () => {
+      element._repoName = 'my-repo';
+      element.params = {view: 'repo', repo: 'my-repo'};
+      const selected = {
+        view: 'repo',
+        detailType: undefined,
+        parent: 'my-repo',
+      };
+      assert.isTrue(element._selectedIsCurrentPage(selected));
+      selected.parent = 'my-second-repo';
+      assert.isFalse(element._selectedIsCurrentPage(selected));
+      selected.detailType = 'detailType';
+      assert.isFalse(element._selectedIsCurrentPage(selected));
+    });
+
     suite('_computeSelectedClass', () => {
       setup(() => {
         sandbox.stub(element.$.restAPI, 'getAccountCapabilities', () => {
diff --git a/polygerrit-ui/app/elements/admin/gr-permission/gr-permission.html b/polygerrit-ui/app/elements/admin/gr-permission/gr-permission.html
index 4a3bbc9..22f461b 100644
--- a/polygerrit-ui/app/elements/admin/gr-permission/gr-permission.html
+++ b/polygerrit-ui/app/elements/admin/gr-permission/gr-permission.html
@@ -115,7 +115,8 @@
                 group-name="[[_computeGroupName(groups, rule.id)]]"
                 permission="[[permission.id]]"
                 rule="{{rule}}"
-                section="[[section]]"></gr-rule-editor>
+                section="[[section]]"
+                on-added-rule-removed="_handleAddedRuleRemoved"></gr-rule-editor>
           </template>
           <div id="addRule">
             <gr-autocomplete
diff --git a/polygerrit-ui/app/elements/admin/gr-permission/gr-permission.js b/polygerrit-ui/app/elements/admin/gr-permission/gr-permission.js
index 01caf1d..31d371d 100644
--- a/polygerrit-ui/app/elements/admin/gr-permission/gr-permission.js
+++ b/polygerrit-ui/app/elements/admin/gr-permission/gr-permission.js
@@ -25,6 +25,11 @@
    * @event access-modified
    */
 
+  /**
+   * Fired when a permission that was previously added was removed.
+   * @event added-permission-removed
+   */
+
   Polymer({
     is: 'gr-permission',
 
@@ -117,6 +122,12 @@
       }
     },
 
+    _handleAddedRuleRemoved(e) {
+      const index = e.model.index;
+      this._rules = this._rules.slice(0, index)
+          .concat(this._rules.slice(index + 1, this._rules.length));
+    },
+
     _handleValueChange() {
       this.permission.value.modified = true;
       // Allows overall access page to know a change has been made.
@@ -124,6 +135,10 @@
     },
 
     _handleRemovePermission() {
+      if (this.permission.value.added) {
+        this.dispatchEvent(new CustomEvent('added-permission-removed',
+            {bubbles: true}));
+      }
       this._deleted = true;
       this.permission.value.deleted = true;
       this.dispatchEvent(new CustomEvent('access-modified', {bubbles: true}));
diff --git a/polygerrit-ui/app/elements/admin/gr-permission/gr-permission_test.html b/polygerrit-ui/app/elements/admin/gr-permission/gr-permission_test.html
index 6799d10..e29c4a2 100644
--- a/polygerrit-ui/app/elements/admin/gr-permission/gr-permission_test.html
+++ b/polygerrit-ui/app/elements/admin/gr-permission/gr-permission_test.html
@@ -327,11 +327,36 @@
         assert.equal(Object.keys(element.permission.value.rules).length, 2);
       });
 
+      test('removing an added rule', () => {
+        element.name = 'Priority';
+        element.section = 'refs/*';
+        element.groups = {};
+        element.$.groupAutocomplete.text = 'new group name';
+        assert.equal(element._rules.length, 2);
+        element.$$('gr-rule-editor').fire('added-rule-removed');
+        flushAsynchronousOperations();
+        assert.equal(element._rules.length, 1);
+      });
+
+      test('removing an added permission', () => {
+        const removeStub = sandbox.stub();
+        element.addEventListener('added-permission-removed', removeStub);
+        element.editing = true;
+        element.name = 'Priority';
+        element.section = 'refs/*';
+        element.permission.value.added = true;
+        MockInteractions.tap(element.$.removeBtn);
+        assert.isTrue(removeStub.called);
+      });
+
       test('removing the permission', () => {
         element.editing = true;
         element.name = 'Priority';
         element.section = 'refs/*';
 
+        const removeStub = sandbox.stub();
+        element.addEventListener('added-permission-removed', removeStub);
+
         assert.isFalse(element.$.permission.classList.contains('deleted'));
         assert.isFalse(element._deleted);
         MockInteractions.tap(element.$.removeBtn);
@@ -340,6 +365,7 @@
         MockInteractions.tap(element.$.undoRemoveBtn);
         assert.isFalse(element.$.permission.classList.contains('deleted'));
         assert.isFalse(element._deleted);
+        assert.isFalse(removeStub.called);
       });
 
       test('modify a permission', () => {
diff --git a/polygerrit-ui/app/elements/admin/gr-repo-access/gr-repo-access.html b/polygerrit-ui/app/elements/admin/gr-repo-access/gr-repo-access.html
index acaf6dd..0ceac7c 100644
--- a/polygerrit-ui/app/elements/admin/gr-repo-access/gr-repo-access.html
+++ b/polygerrit-ui/app/elements/admin/gr-repo-access/gr-repo-access.html
@@ -111,7 +111,8 @@
               section="{{section}}"
               labels="[[_labels]]"
               editing="[[_editing]]"
-              groups="[[_groups]]"></gr-access-section>
+              groups="[[_groups]]"
+              on-added-section-removed="_handleAddedSectionRemoved"></gr-access-section>
         </template>
         <div class="referenceContainer">
           <gr-button id="addReferenceBtn"
diff --git a/polygerrit-ui/app/elements/admin/gr-repo-access/gr-repo-access.js b/polygerrit-ui/app/elements/admin/gr-repo-access/gr-repo-access.js
index 853e316..f307090 100644
--- a/polygerrit-ui/app/elements/admin/gr-repo-access/gr-repo-access.js
+++ b/polygerrit-ui/app/elements/admin/gr-repo-access/gr-repo-access.js
@@ -243,6 +243,12 @@
       return inheritsFrom ? 'show' : '';
     },
 
+    _handleAddedSectionRemoved(e) {
+      const index = e.model.index;
+      this._sections = this._sections.slice(0, index)
+          .concat(this._sections.slice(index + 1, this._sections.length));
+    },
+
     _handleEditingChanged(editing, editingOld) {
       // Ignore when editing gets set initially.
       if (!editingOld || editing) { return; }
diff --git a/polygerrit-ui/app/elements/admin/gr-repo-access/gr-repo-access_test.html b/polygerrit-ui/app/elements/admin/gr-repo-access/gr-repo-access_test.html
index 66fece3..2f0b1b8 100644
--- a/polygerrit-ui/app/elements/admin/gr-repo-access/gr-repo-access_test.html
+++ b/polygerrit-ui/app/elements/admin/gr-repo-access/gr-repo-access_test.html
@@ -301,6 +301,14 @@
         flushAsynchronousOperations();
       });
 
+      test('removing an added section', () => {
+        element.editing = true;
+        assert.equal(element._sections.length, 1);
+        element.$$('gr-access-section').fire('added-section-removed');
+        flushAsynchronousOperations();
+        assert.equal(element._sections.length, 0);
+      });
+
       test('button visibility for non admin', () => {
         assert.equal(getComputedStyle(element.$.saveBtn).display, 'none');
         assert.equal(getComputedStyle(element.$.editBtn).display, 'none');
diff --git a/polygerrit-ui/app/elements/admin/gr-rule-editor/gr-rule-editor.js b/polygerrit-ui/app/elements/admin/gr-rule-editor/gr-rule-editor.js
index d4c5fd1..503811d 100644
--- a/polygerrit-ui/app/elements/admin/gr-rule-editor/gr-rule-editor.js
+++ b/polygerrit-ui/app/elements/admin/gr-rule-editor/gr-rule-editor.js
@@ -23,6 +23,11 @@
    * @event access-modified
    */
 
+  /**
+   * Fired when a rule that was previously added was removed.
+   * @event added-rule-removed
+   */
+
   const PRIORITY_OPTIONS = [
     'BATCH',
     'INTERACTIVE',
@@ -202,6 +207,10 @@
     },
 
     _handleRemoveRule() {
+      if (this.rule.value.added) {
+        this.dispatchEvent(new CustomEvent('added-rule-removed',
+            {bubbles: true}));
+      }
       this._deleted = true;
       this.rule.value.deleted = true;
       this.dispatchEvent(new CustomEvent('access-modified', {bubbles: true}));
diff --git a/polygerrit-ui/app/elements/admin/gr-rule-editor/gr-rule-editor_test.html b/polygerrit-ui/app/elements/admin/gr-rule-editor/gr-rule-editor_test.html
index 3e3bcfb..666a8f6 100644
--- a/polygerrit-ui/app/elements/admin/gr-rule-editor/gr-rule-editor_test.html
+++ b/polygerrit-ui/app/elements/admin/gr-rule-editor/gr-rule-editor_test.html
@@ -319,6 +319,7 @@
         element.section = 'refs/*';
         element._setupValues(element.rule);
         flushAsynchronousOperations();
+        element.rule.value.added = true;
       });
 
       test('_ruleValues and _originalRuleValues are set correctly', () => {
@@ -328,9 +329,9 @@
         const expectedRuleValue = {
           action: 'ALLOW',
           force: false,
+          added: true,
         };
         assert.deepEqual(element.rule.value, expectedRuleValue);
-        assert.deepEqual(element._originalRuleValues, expectedRuleValue);
         test('values are set correctly', () => {
           assert.equal(element.$.action.bindValue, expectedRuleValue.action);
           assert.equal(element.$.force.bindValue, expectedRuleValue.action);
@@ -346,6 +347,15 @@
         // The original value should now differ from the rule values.
         assert.notDeepEqual(element._originalRuleValues, element.rule.value);
       });
+
+      test('remove value', () => {
+        element.editing = true;
+        const removeStub = sandbox.stub();
+        element.addEventListener('added-rule-removed', removeStub);
+        MockInteractions.tap(element.$.removeBtn);
+        flushAsynchronousOperations();
+        assert.isTrue(removeStub.called);
+      });
     });
 
     suite('already existing rule with labels', () => {
@@ -389,10 +399,13 @@
       });
 
       test('modify value', () => {
+        const removeStub = sandbox.stub();
+        element.addEventListener('added-rule-removed', removeStub);
         assert.isNotOk(element.rule.value.modified);
         Polymer.dom(element.root).querySelector('#labelMin').bindValue = 1;
         flushAsynchronousOperations();
         assert.isTrue(element.rule.value.modified);
+        assert.isFalse(removeStub.called);
 
         // The original value should now differ from the rule values.
         assert.notDeepEqual(element._originalRuleValues, element.rule.value);
@@ -417,6 +430,7 @@
         element.section = 'refs/*';
         element._setupValues(element.rule);
         flushAsynchronousOperations();
+        element.rule.value.added = true;
       });
 
       test('_ruleValues and _originalRuleValues are set correctly', () => {
@@ -429,9 +443,9 @@
           max: element.label.values[element.label.values.length - 1].value,
           min: element.label.values[0].value,
           action: 'ALLOW',
+          added: true,
         };
         assert.deepEqual(element.rule.value, expectedRuleValue);
-        assert.deepEqual(element._originalRuleValues, expectedRuleValue);
         test('values are set correctly', () => {
           assert.equal(
               element.$.action.bindValue,
@@ -507,6 +521,7 @@
         element.section = 'refs/*';
         element._setupValues(element.rule);
         flushAsynchronousOperations();
+        element.rule.value.added = true;
       });
 
       test('_ruleValues and _originalRuleValues are set correctly', () => {
@@ -516,9 +531,9 @@
         const expectedRuleValue = {
           action: 'ALLOW',
           force: false,
+          added: true,
         };
         assert.deepEqual(element.rule.value, expectedRuleValue);
-        assert.deepEqual(element._originalRuleValues, expectedRuleValue);
         test('values are set correctly', () => {
           assert.equal(element.$.action.bindValue, expectedRuleValue.action);
           assert.equal(element.$.force.bindValue, expectedRuleValue.action);
@@ -576,44 +591,5 @@
         assert.notDeepEqual(element._originalRuleValues, element.rule.value);
       });
     });
-
-    suite('new edit rule', () => {
-      setup(() => {
-        element.group = 'Group Name';
-        element.permission = 'editTopicName';
-        element.rule = {
-          id: '123',
-        };
-        element.section = 'refs/*';
-        element._setupValues(element.rule);
-        flushAsynchronousOperations();
-      });
-
-      test('_ruleValues and _originalRuleValues are set correctly', () => {
-        // Since the element does not already have default values, they should
-        // be set. The original values should be set to those too.
-        assert.isNotOk(element.rule.value.modified);
-        const expectedRuleValue = {
-          action: 'ALLOW',
-          force: false,
-        };
-        assert.deepEqual(element.rule.value, expectedRuleValue);
-        assert.deepEqual(element._originalRuleValues, expectedRuleValue);
-        test('values are set correctly', () => {
-          assert.equal(element.$.action.bindValue, expectedRuleValue.action);
-          assert.equal(element.$.force.bindValue, expectedRuleValue.action);
-        });
-      });
-
-      test('modify value', () => {
-        assert.isNotOk(element.rule.value.modified);
-        element.$.force.bindValue = true;
-        flushAsynchronousOperations();
-        assert.isTrue(element.rule.value.modified);
-
-        // The original value should now differ from the rule values.
-        assert.notDeepEqual(element._originalRuleValues, element.rule.value);
-      });
-    });
   });
 </script>
diff --git a/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view.html b/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view.html
index 101899b..1935962 100644
--- a/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view.html
+++ b/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view.html
@@ -15,10 +15,11 @@
 limitations under the License.
 -->
 
-<link rel="import" href="../../../behaviors/rest-client-behavior/rest-client-behavior.html">
 <link rel="import" href="../../../bower_components/polymer/polymer.html">
+<link rel="import" href="../../../behaviors/rest-client-behavior/rest-client-behavior.html">
 <link rel="import" href="../../../styles/shared-styles.html">
 <link rel="import" href="../../change-list/gr-change-list/gr-change-list.html">
+<link rel="import" href="../../core/gr-reporting/gr-reporting.html">
 <link rel="import" href="../../shared/gr-rest-api-interface/gr-rest-api-interface.html">
 <link rel="import" href="../gr-user-header/gr-user-header.html">
 
@@ -62,6 +63,7 @@
           sections="[[_results]]"></gr-change-list>
     </div>
     <gr-rest-api-interface id="restAPI"></gr-rest-api-interface>
+    <gr-reporting id="reporting"></gr-reporting>
   </template>
   <script src="gr-dashboard-view.js"></script>
 </dom-module>
diff --git a/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view.js b/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view.js
index b6e06d8..f86c98c 100644
--- a/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view.js
+++ b/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view.js
@@ -230,6 +230,8 @@
             };
           });
         });
+      }).then(() => {
+        this.$.reporting.dashboardDisplayed();
       }).catch(err => {
         this._loading = false;
         console.warn(err);
diff --git a/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view_test.html b/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view_test.html
index a3aa4f2..a1da018 100644
--- a/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view_test.html
+++ b/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view_test.html
@@ -55,7 +55,7 @@
       });
       const paramsChanged = element._paramsChanged.bind(element);
       sandbox.stub(element, '_paramsChanged', params => {
-        paramsChanged(params).then(resolver());
+        paramsChanged(params).then(() => resolver());
       });
     });
 
@@ -262,5 +262,17 @@
         dashboard: 'dashboard',
       };
     });
+
+    test('params change triggers dashboardDisplayed()', () => {
+      sandbox.stub(element.$.reporting, 'dashboardDisplayed');
+      element.params = {
+        view: Gerrit.Nav.View.DASHBOARD,
+        project: 'project',
+        dashboard: 'dashboard',
+      };
+      return paramsChangedPromise.then(() => {
+        assert.isTrue(element.$.reporting.dashboardDisplayed.calledOnce);
+      });
+    });
   });
 </script>
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 88cee4c..0688435 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
@@ -22,6 +22,7 @@
 <link rel="import" href="../../../bower_components/paper-tabs/paper-tabs.html">
 <link rel="import" href="../../../styles/shared-styles.html">
 <link rel="import" href="../../core/gr-navigation/gr-navigation.html">
+<link rel="import" href="../../core/gr-reporting/gr-reporting.html">
 <link rel="import" href="../../diff/gr-comment-api/gr-comment-api.html">
 <link rel="import" href="../../diff/gr-diff-preferences/gr-diff-preferences.html">
 <link rel="import" href="../../edit/gr-edit-constants.html">
@@ -615,6 +616,7 @@
     <gr-js-api-interface id="jsAPI"></gr-js-api-interface>
     <gr-rest-api-interface id="restAPI"></gr-rest-api-interface>
     <gr-comment-api id="commentAPI"></gr-comment-api>
+    <gr-reporting id="reporting"></gr-reporting>
   </template>
   <script src="gr-change-view.js"></script>
 </dom-module>
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 7b31ee3..29ffec8 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
@@ -1210,8 +1210,10 @@
 
       this._reloadComments();
 
+      let reloadPromise;
+
       if (this._patchRange.patchNum) {
-        return Promise.all([
+        reloadPromise = Promise.all([
           this._reloadPatchNumDependentResources(),
           detailCompletes,
         ]).then(() => {
@@ -1222,7 +1224,7 @@
         });
       } else {
         // The patch number is reliant on the change detail request.
-        return detailCompletes.then(() => {
+        reloadPromise = detailCompletes.then(() => {
           this.$.fileList.reload();
           if (!this._latestCommitMessage) {
             this._getLatestCommitMessage();
@@ -1230,6 +1232,10 @@
           return this._getMergeability();
         });
       }
+
+      return reloadPromise.then(() => {
+        this.$.reporting.changeDisplayed();
+      });
     },
 
     /**
diff --git a/polygerrit-ui/app/elements/core/gr-reporting/gr-reporting.js b/polygerrit-ui/app/elements/core/gr-reporting/gr-reporting.js
index e488961..73a29a7 100644
--- a/polygerrit-ui/app/elements/core/gr-reporting/gr-reporting.js
+++ b/polygerrit-ui/app/elements/core/gr-reporting/gr-reporting.js
@@ -38,6 +38,25 @@
     CATEGORY: 'exception',
   };
 
+  const TIMER = {
+    CHANGE_DISPLAYED: 'ChangeDisplayed',
+    DASHBOARD_DISPLAYED: 'DashboardDisplayed',
+    DIFF_VIEW_DISPLAYED: 'DiffViewDisplayed',
+    PLUGINS_LOADED: 'PluginsLoaded',
+    STARTUP_CHANGE_DISPLAYED: 'StartupChangeDisplayed',
+    STARTUP_DASHBOARD_DISPLAYED: 'StartupDashboardDisplayed',
+    STARTUP_DIFF_VIEW_DISPLAYED: 'StartupDiffViewDisplayed',
+    WEB_COMPONENTS_READY: 'WebComponentsReady',
+  };
+
+  const STARTUP_TIMERS = {};
+  STARTUP_TIMERS[TIMER.PLUGINS_LOADED] = 0;
+  STARTUP_TIMERS[TIMER.STARTUP_CHANGE_DISPLAYED] = 0;
+  STARTUP_TIMERS[TIMER.STARTUP_DASHBOARD_DISPLAYED] = 0;
+  STARTUP_TIMERS[TIMER.STARTUP_DIFF_VIEW_DISPLAYED] = 0;
+  // WebComponentsReady timer is triggered from gr-router.
+  STARTUP_TIMERS[TIMER.WEB_COMPONENTS_READY] = 0;
+
   const INTERACTION_TYPE = 'interaction';
 
   const pending = [];
@@ -81,8 +100,8 @@
       category: String,
 
       _baselines: {
-        type: Array,
-        value() { return {}; },
+        type: Object,
+        value: STARTUP_TIMERS, // Shared across all instances.
       },
     },
 
@@ -91,7 +110,7 @@
     },
 
     now() {
-      return Math.round(10 * window.performance.now()) / 10;
+      return window.performance.now();
     },
 
     reporter(...args) {
@@ -157,13 +176,46 @@
       }
     },
 
+    beforeLocationChanged() {
+      for (const prop of Object.keys(this._baselines)) {
+        delete this._baselines[prop];
+      }
+      this.time(TIMER.CHANGE_DISPLAYED);
+      this.time(TIMER.DASHBOARD_DISPLAYED);
+      this.time(TIMER.DIFF_VIEW_DISPLAYED);
+    },
+
     locationChanged(page) {
       this.reporter(
           NAVIGATION.TYPE, NAVIGATION.CATEGORY, NAVIGATION.PAGE, page);
     },
 
+    dashboardDisplayed() {
+      if (this._baselines.hasOwnProperty(TIMER.STARTUP_DASHBOARD_DISPLAYED)) {
+        this.timeEnd(TIMER.STARTUP_DASHBOARD_DISPLAYED);
+      } else {
+        this.timeEnd(TIMER.DASHBOARD_DISPLAYED);
+      }
+    },
+
+    changeDisplayed() {
+      if (this._baselines.hasOwnProperty(TIMER.STARTUP_CHANGE_DISPLAYED)) {
+        this.timeEnd(TIMER.STARTUP_CHANGE_DISPLAYED);
+      } else {
+        this.timeEnd(TIMER.CHANGE_DISPLAYED);
+      }
+    },
+
+    diffViewDisplayed() {
+      if (this._baselines.hasOwnProperty(TIMER.STARTUP_DIFF_VIEW_DISPLAYED)) {
+        this.timeEnd(TIMER.STARTUP_DIFF_VIEW_DISPLAYED);
+      } else {
+        this.timeEnd(TIMER.DIFF_VIEW_DISPLAYED);
+      }
+    },
+
     pluginsLoaded() {
-      this.timeEnd('PluginsLoaded');
+      this.timeEnd(TIMER.PLUGINS_LOADED);
     },
 
     /**
@@ -177,7 +229,8 @@
      * Finish named timer and report it to server.
      */
     timeEnd(name) {
-      const baseTime = this._baselines[name] || 0;
+      if (!this._baselines.hasOwnProperty(name)) { return; }
+      const baseTime = this._baselines[name];
       const time = Math.round(this.now() - baseTime);
       this.reporter(TIMING.TYPE, TIMING.CATEGORY, name, time);
       delete this._baselines[name];
@@ -191,4 +244,5 @@
   window.GrReporting = GrReporting;
   // Expose onerror installation so it would be accessible from tests.
   window.GrReporting._catchErrors = catchErrors;
+  window.GrReporting.STARTUP_TIMERS = Object.assign({}, STARTUP_TIMERS);
 })();
diff --git a/polygerrit-ui/app/elements/core/gr-reporting/gr-reporting_test.html b/polygerrit-ui/app/elements/core/gr-reporting/gr-reporting_test.html
index 8a94e99..3965c7d 100644
--- a/polygerrit-ui/app/elements/core/gr-reporting/gr-reporting_test.html
+++ b/polygerrit-ui/app/elements/core/gr-reporting/gr-reporting_test.html
@@ -45,6 +45,7 @@
       sandbox = sinon.sandbox.create();
       clock = sinon.useFakeTimers(NOW_TIME);
       element = fixture('basic');
+      element._baselines = Object.assign({}, GrReporting.STARTUP_TIMERS);
       fakePerformance = {
         navigationStart: 1,
         loadEventEnd: 2,
@@ -53,6 +54,7 @@
           {get() { return fakePerformance; }});
       sandbox.stub(element, 'reporter');
     });
+
     teardown(() => {
       sandbox.restore();
       clock.restore();
@@ -67,6 +69,14 @@
       ));
     });
 
+    test('WebComponentsReady', () => {
+      sandbox.stub(element, 'now').returns(42);
+      element.timeEnd('WebComponentsReady');
+      assert.isTrue(element.reporter.calledWithExactly(
+          'timing-report', 'UI Latency', 'WebComponentsReady', 42
+      ));
+    });
+
     test('pageLoaded', () => {
       element.pageLoaded();
       assert.isTrue(
@@ -76,6 +86,49 @@
       );
     });
 
+    test('beforeLocationChanged', () => {
+      element._baselines['garbage'] = 'monster';
+      sandbox.stub(element, 'time');
+      element.beforeLocationChanged();
+      assert.isTrue(element.time.calledWithExactly('DashboardDisplayed'));
+      assert.isTrue(element.time.calledWithExactly('ChangeDisplayed'));
+      assert.isTrue(element.time.calledWithExactly('DiffViewDisplayed'));
+      assert.isFalse(element._baselines.hasOwnProperty('garbage'));
+    });
+
+    test('changeDisplayed', () => {
+      sandbox.spy(element, 'timeEnd');
+      element.changeDisplayed();
+      assert.isFalse(
+          element.timeEnd.calledWithExactly('ChangeDisplayed'));
+      assert.isTrue(
+          element.timeEnd.calledWithExactly('StartupChangeDisplayed'));
+      element.changeDisplayed();
+      assert.isTrue(element.timeEnd.calledWithExactly('ChangeDisplayed'));
+    });
+
+    test('diffViewDisplayed', () => {
+      sandbox.spy(element, 'timeEnd');
+      element.diffViewDisplayed();
+      assert.isFalse(
+          element.timeEnd.calledWithExactly('DiffViewDisplayed'));
+      assert.isTrue(
+          element.timeEnd.calledWithExactly('StartupDiffViewDisplayed'));
+      element.diffViewDisplayed();
+      assert.isTrue(element.timeEnd.calledWithExactly('DiffViewDisplayed'));
+    });
+
+    test('dashboardDisplayed', () => {
+      sandbox.spy(element, 'timeEnd');
+      element.dashboardDisplayed();
+      assert.isFalse(
+          element.timeEnd.calledWithExactly('DashboardDisplayed'));
+      assert.isTrue(
+          element.timeEnd.calledWithExactly('StartupDashboardDisplayed'));
+      element.dashboardDisplayed();
+      assert.isTrue(element.timeEnd.calledWithExactly('DashboardDisplayed'));
+    });
+
     test('time and timeEnd', () => {
       const nowStub = sandbox.stub(element, 'now').returns(0);
       element.time('foo');
@@ -117,11 +170,13 @@
 
       test('reports if plugins are loaded', () => {
         Gerrit._arePluginsLoaded.returns(true);
-        element.timeEnd('foo');
+        element.pluginsLoaded();
         assert.isTrue(element.defaultReporter.called);
       });
 
       test('reports cached events preserving order', () => {
+        element.time('foo');
+        element.time('bar');
         Gerrit._arePluginsLoaded.returns(false);
         element.timeEnd('foo');
         Gerrit._arePluginsLoaded.returns(true);
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 36648ab..8af7301 100644
--- a/polygerrit-ui/app/elements/core/gr-router/gr-router.js
+++ b/polygerrit-ui/app/elements/core/gr-router/gr-router.js
@@ -199,6 +199,7 @@
         type: Object,
         value: app,
       },
+      _isRedirecting: Boolean,
     },
 
     behaviors: [
@@ -217,6 +218,7 @@
     },
 
     _redirect(url) {
+      this._isRedirecting = true;
       page.redirect(url);
     },
 
@@ -670,6 +672,14 @@
           params => this._generateWeblinks(params)
       );
 
+      page.exit('*', (ctx, next) => {
+        if (!this._isRedirecting) {
+          this.$.reporting.beforeLocationChanged();
+        }
+        this._isRedirecting = false;
+        next();
+      });
+
       // Middleware
       page((ctx, next) => {
         document.body.scrollTop = 0;
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.html b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.html
index df3a2c5..1fc99b1 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.html
@@ -23,6 +23,7 @@
 <link rel="import" href="../../../bower_components/iron-dropdown/iron-dropdown.html">
 <link rel="import" href="../../../styles/shared-styles.html">
 <link rel="import" href="../../core/gr-navigation/gr-navigation.html">
+<link rel="import" href="../../core/gr-reporting/gr-reporting.html">
 <link rel="import" href="../../shared/gr-button/gr-button.html">
 <link rel="import" href="../../shared/gr-count-string-formatter/gr-count-string-formatter.html">
 <link rel="import" href="../../shared/gr-dropdown-list/gr-dropdown-list.html">
@@ -346,6 +347,7 @@
     <gr-storage id="storage"></gr-storage>
     <gr-diff-cursor id="cursor"></gr-diff-cursor>
     <gr-comment-api id="commentAPI"></gr-comment-api>
+    <gr-reporting id="reporting"></gr-reporting>
   </template>
   <script src="gr-diff-view.js"></script>
 </dom-module>
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.js b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.js
index f4125a0..5df640e 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.js
@@ -618,7 +618,7 @@
       promises.push(this._getChangeEdit(this._changeNum));
 
       this._loading = true;
-      Promise.all(promises).then(r => {
+      return Promise.all(promises).then(r => {
         const edit = r[4];
         if (edit) {
           this.set('_change.revisions.' + edit.commit.commit, {
@@ -629,7 +629,9 @@
         }
         this._loading = false;
         this.$.diff.comments = this._commentsForDiff;
-        this.$.diff.reload();
+        return this.$.diff.reload();
+      }).then(() => {
+        this.$.reporting.diffViewDisplayed();
       });
     },
 
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view_test.html b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view_test.html
index 46dbbdc..620286b 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view_test.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view_test.html
@@ -71,6 +71,23 @@
       sandbox.restore();
     });
 
+    test('params change triggers diffViewDisplayed()', () => {
+      sandbox.stub(element.$.reporting, 'diffViewDisplayed');
+      sandbox.stub(element.$.diff, 'reload').returns(Promise.resolve());
+      sandbox.spy(element, '_paramsChanged');
+      element.params = {
+        view: Gerrit.Nav.View.DIFF,
+        changeNum: '42',
+        patchNum: '2',
+        basePatchNum: '1',
+        path: '/COMMIT_MSG',
+      };
+
+      return element._paramsChanged.returnValues[0].then(() => {
+        assert.isTrue(element.$.reporting.diffViewDisplayed.calledOnce);
+      });
+    });
+
     test('toggle left diff with a hotkey', () => {
       const toggleLeftDiffStub = sandbox.stub(element.$.diff, 'toggleLeftDiff');
       MockInteractions.pressAndReleaseKeyOn(element, 65, 'shift', 'a');
diff --git a/polygerrit-ui/app/styles/gr-menu-page-styles.html b/polygerrit-ui/app/styles/gr-menu-page-styles.html
index 6122a21..4adbeda 100644
--- a/polygerrit-ui/app/styles/gr-menu-page-styles.html
+++ b/polygerrit-ui/app/styles/gr-menu-page-styles.html
@@ -25,7 +25,12 @@
         margin: 2em auto;
         max-width: 50em;
       }
-      main.table {
+      .mainHeader {
+        margin-left: 14em;
+        padding: 1em 0 1em 2em;
+      }
+      main.table,
+      .mainHeader {
         margin-top: 0;
         margin-right: 0;
         margin-left: 14em;
@@ -57,6 +62,10 @@
         main.table {
           margin: 0;
         }
+        .mainHeader {
+          margin-left: 0;
+          padding: .5em 0 .5em 1em;
+        }
       }
     </style>
   </template>