Merge "Exclude bin directory from IntelliJ Bazel project"
diff --git a/plugins/codemirror-editor b/plugins/codemirror-editor
index c97e280..ee50e45 160000
--- a/plugins/codemirror-editor
+++ b/plugins/codemirror-editor
@@ -1 +1 @@
-Subproject commit c97e2806532cff00fea6424cde0d440f9ea5016d
+Subproject commit ee50e45b449e282ed78917175daf8b359da8d943
diff --git a/polygerrit-ui/app/behaviors/gr-tooltip-behavior/gr-tooltip-behavior.js b/polygerrit-ui/app/behaviors/gr-tooltip-behavior/gr-tooltip-behavior.js
index 4d53631..04d8b6e 100644
--- a/polygerrit-ui/app/behaviors/gr-tooltip-behavior/gr-tooltip-behavior.js
+++ b/polygerrit-ui/app/behaviors/gr-tooltip-behavior/gr-tooltip-behavior.js
@@ -51,7 +51,6 @@
 
     detached() {
       this._handleHideTooltip();
-      this.unlisten(window, 'scroll', '_handleWindowScroll');
     },
 
     _setupTooltipListeners() {
@@ -59,9 +58,6 @@
       this._hasSetupTooltipListeners = true;
 
       this.addEventListener('mouseenter', this._handleShowTooltip.bind(this));
-      this.addEventListener('mouseleave', this._handleHideTooltip.bind(this));
-      this.addEventListener('tap', this._handleHideTooltip.bind(this));
-      this.listen(window, 'scroll', '_handleWindowScroll');
     },
 
     _handleShowTooltip(e) {
@@ -91,6 +87,9 @@
       tooltip.style.visibility = null;
 
       this._tooltip = tooltip;
+      this.listen(window, 'scroll', '_handleWindowScroll');
+      this.listen(this, 'mouseleave', '_handleHideTooltip');
+      this.listen(this, 'tap', '_handleHideTooltip');
     },
 
     _handleHideTooltip(e) {
@@ -100,6 +99,9 @@
         return;
       }
 
+      this.unlisten(window, 'scroll', '_handleWindowScroll');
+      this.unlisten(this, 'mouseleave', '_handleHideTooltip');
+      this.unlisten(this, 'tap', '_handleHideTooltip');
       this.setAttribute('title', this._titleText);
       if (this._tooltip && this._tooltip.parentNode) {
         this._tooltip.parentNode.removeChild(this._tooltip);
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 29ffec8..cb9f4c5 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
@@ -57,6 +57,8 @@
     UNIFIED: 'UNIFIED_DIFF',
   };
 
+  const CHANGE_DATA_TIMING_LABEL = 'ChangeDataLoaded';
+
   Polymer({
     is: 'gr-change-view',
 
@@ -624,6 +626,8 @@
       this.$.fileList.collapseAllDiffs();
       this._patchRange = patchRange;
 
+      // If the change has already been loaded and the parameter change is only
+      // in the patch range, then don't do a full reload.
       if (this._initialLoadComplete && patchChanged) {
         if (patchRange.patchNum == null) {
           patchRange.patchNum = this.computeLatestPatchNum(this._allPatchSets);
@@ -637,7 +641,7 @@
       this._changeNum = value.changeNum;
       this.$.relatedChanges.clear();
 
-      this._reload().then(() => {
+      this._reload(true).then(() => {
         this._performPostLoadTasks();
       });
     },
@@ -651,7 +655,6 @@
     },
 
     _performPostLoadTasks() {
-      this.$.relatedChanges.reload();
       this._maybeShowReplyDialog();
       this._maybeShowRevertDialog();
 
@@ -1199,43 +1202,102 @@
           });
     },
 
-    _reload() {
+    /**
+     * Reload the change.
+     * @param {boolean=} opt_reloadRelatedChanges Reloads the related chanegs
+     *     when true.
+     * @return {Promise} A promise that resolves when the core data has loaded.
+     *     Some non-core data loading may still be in-flight when the core data
+     *     promise resolves.
+     */
+    _reload(opt_reloadRelatedChanges) {
       this._loading = true;
       this._relatedChangesCollapsed = true;
 
-      const detailCompletes = this._getChangeDetail().then(() => {
-        this._loading = false;
-        this._getProjectConfig();
-      });
+      // Array to house all promises related to data requests.
+      const allDataPromises = [];
 
-      this._reloadComments();
+      // Resolves when the change detail and the edit patch set (if available)
+      // are loaded.
+      const detailCompletes = this._getChangeDetail();
+      allDataPromises.push(detailCompletes);
 
-      let reloadPromise;
+      // Resolves when the loading flag is set to false, meaning that some
+      // change content may start appearing.
+      const loadingFlagSet = detailCompletes
+          .then(() => { this._loading = false; });
 
+      // Resolves when the project config has loaded.
+      const projectConfigLoaded = detailCompletes
+          .then(() => this._getProjectConfig());
+      allDataPromises.push(projectConfigLoaded);
+
+      // Resolves when change comments have loaded (comments, drafts and robot
+      // comments).
+      const commentsLoaded = this._reloadComments();
+      allDataPromises.push(commentsLoaded);
+
+      let coreDataPromise;
+
+      // If the patch number is specified
       if (this._patchRange.patchNum) {
-        reloadPromise = Promise.all([
-          this._reloadPatchNumDependentResources(),
-          detailCompletes,
-        ]).then(() => {
-          return Promise.all([
-            this._getMergeability(),
-            this.$.actions.reload(),
-          ]);
-        });
+        // Because a specific patchset is specified, reload the resources that
+        // are keyed by patch number or patch range.
+        const patchResourcesLoaded = this._reloadPatchNumDependentResources();
+        allDataPromises.push(patchResourcesLoaded);
+
+        // Promise resolves when the change detail and patch dependent resources
+        // have loaded.
+        const detailAndPatchResourcesLoaded =
+            Promise.all([patchResourcesLoaded, loadingFlagSet]);
+
+        // Promise resolves when mergeability information has loaded.
+        const mergeabilityLoaded = detailAndPatchResourcesLoaded
+            .then(() => this._getMergeability());
+        allDataPromises.push(mergeabilityLoaded);
+
+        // Promise resovles when the change actions have loaded.
+        const actionsLoaded = detailAndPatchResourcesLoaded
+            .then(() => this.$.actions.reload());
+        allDataPromises.push(actionsLoaded);
+
+        // The core data is loaded when both mergeability and actions are known.
+        coreDataPromise = Promise.all([mergeabilityLoaded, actionsLoaded]);
       } else {
-        // The patch number is reliant on the change detail request.
-        reloadPromise = detailCompletes.then(() => {
-          this.$.fileList.reload();
-          if (!this._latestCommitMessage) {
-            this._getLatestCommitMessage();
-          }
-          return this._getMergeability();
+        // Resolves when the file list has loaded.
+        const fileListReload = loadingFlagSet
+            .then(() => this.$.fileList.reload());
+        allDataPromises.push(fileListReload);
+
+        const latestCommitMessageLoaded = loadingFlagSet.then(() => {
+          // If the latest commit message is known, there is nothing to do.
+          if (this._latestCommitMessage) { return Promise.resolve(); }
+          return this._getLatestCommitMessage();
         });
+        allDataPromises.push(latestCommitMessageLoaded);
+
+        // Promise resolves when mergeability information has loaded.
+        const mergeabilityLoaded = loadingFlagSet
+            .then(() => this._getMergeability());
+        allDataPromises.push(mergeabilityLoaded);
+
+        // Core data is loaded when mergeability has been loaded.
+        coreDataPromise = mergeabilityLoaded;
       }
 
-      return reloadPromise.then(() => {
-        this.$.reporting.changeDisplayed();
+      if (opt_reloadRelatedChanges) {
+        const relatedChangesLoaded = coreDataPromise
+            .then(() => this.$.relatedChanges.reload());
+        allDataPromises.push(relatedChangesLoaded);
+      }
+
+      this.$.reporting.time(CHANGE_DATA_TIMING_LABEL);
+      Promise.all(allDataPromises).then(() => {
+        this.$.reporting.timeEnd(CHANGE_DATA_TIMING_LABEL);
       });
+
+      return coreDataPromise
+          .then(() => { this.$.reporting.changeDisplayed(); });
     },
 
     /**
diff --git a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.html b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.html
index 81c6d99..540df98 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.html
@@ -108,7 +108,6 @@
         cursor: pointer;
       }
       .content {
-        overflow: hidden;
         /* Set min width since setting width on table cells still
            allows them to shrink. Do not set max width because
            CJK (Chinese-Japanese-Korean) glyphs have variable width */
diff --git a/polygerrit-ui/app/elements/gr-app.html b/polygerrit-ui/app/elements/gr-app.html
index de62646..e4b2577 100644
--- a/polygerrit-ui/app/elements/gr-app.html
+++ b/polygerrit-ui/app/elements/gr-app.html
@@ -19,6 +19,11 @@
   if (localStorage.getItem('USE_SHADOW_DOM') === 'true') {
     window.Polymer = {
       dom: 'shadow',
+      passiveTouchGestures: true,
+    };
+  } else if (!window.Polymer) {
+    window.Polymer = {
+      passiveTouchGestures: true,
     };
   }
 </script>
diff --git a/polygerrit-ui/app/styles/themes/app-theme.html b/polygerrit-ui/app/styles/themes/app-theme.html
index 69262c9..4500e10 100644
--- a/polygerrit-ui/app/styles/themes/app-theme.html
+++ b/polygerrit-ui/app/styles/themes/app-theme.html
@@ -42,7 +42,7 @@
   --table-header-background-color: #fafafa;
   --table-subheader-background-color: #eaeaea;
 
-  --chip-background-color: var(--header-background-color);
+  --chip-background-color: #eee;
 
   --dropdown-background-color: #fff;