Merge "Fix diff-builder leaks"
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-element.js b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-element.js
index c9d5d78..bb617f0 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-element.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-element.js
@@ -113,6 +113,14 @@
     };
   }
 
+  /** @override */
+  detached() {
+    super.detached();
+    if (this._builder) {
+      this._builder.clear();
+    }
+  }
+
   get diffElement() {
     return this.queryEffectiveChildren('#diffTable');
   }
@@ -144,6 +152,9 @@
     // Stop the processor if it's running.
     this.cancel();
 
+    if (this._builder) {
+      this._builder.clear();
+    }
     this._builder = this._getDiffBuilder(this.diff, prefs);
 
     this.$.processor.context = prefs.context;
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 1cabd82..04f1548 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
@@ -62,13 +62,22 @@
     throw Error('Invalid line length from preferences.');
   }
 
+  this._layerUpdateListener = this._handleLayerUpdate.bind(this);
   for (const layer of this.layers) {
     if (layer.addListener) {
-      layer.addListener(this._handleLayerUpdate.bind(this));
+      layer.addListener(this._layerUpdateListener);
     }
   }
 }
 
+GrDiffBuilder.prototype.clear = function() {
+  for (const layer of this.layers) {
+    if (layer.removeListener) {
+      layer.removeListener(this._layerUpdateListener);
+    }
+  }
+};
+
 GrDiffBuilder.GroupType = {
   ADDED: 'b',
   BOTH: 'ab',
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 cb299de..abcdc73 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
@@ -314,12 +314,19 @@
     });
   }
 
+  /** @override */
+  detached() {
+    super.detached();
+    this.clear();
+  }
+
   /**
    * @param {boolean=} shouldReportMetric indicate a new Diff Page. This is a
    * signal to report metrics event that started on location change.
    * @return {!Promise}
    **/
   reload(shouldReportMetric) {
+    this.clear();
     this._loading = true;
     this._errorMessage = null;
     const whitespaceLevel = this._getIgnoreWhitespace();
@@ -398,6 +405,11 @@
         .then(() => { this._loading = false; });
   }
 
+  clear() {
+    this.$.jsAPI.disposeDiffLayers(this.path);
+    this._layers = [];
+  }
+
   _getCoverageData() {
     const {changeNum, path, patchRange: {basePatchNum, patchNum}} = this;
     this.$.jsAPI.getCoverageAnnotationApi().
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 9c9997c..c774f1a 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
@@ -107,6 +107,10 @@
     this._listeners.push(fn);
   }
 
+  removeListener(fn) {
+    this._listeners = this._listeners.filter(f => f != fn);
+  }
+
   /**
    * Notify Layer listeners of changes to annotations.
    *
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 3b24404..6f73c36 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
@@ -144,7 +144,6 @@
     // path.
     if (annotationLayer._path === path) {
       annotationLayer.notifyListeners(startRange, endRange, side);
-      break;
     }
   }
 };
@@ -153,6 +152,8 @@
  * Should be called to register annotation layers by the framework. Not
  * intended to be called by plugins.
  *
+ * Don't forget to dispose layer.
+ *
  * @param {string} path The file path (eg: /COMMIT_MSG').
  * @param {string} changeNum The Gerrit change number.
  * @param {string} patchNum The Gerrit patch number.
@@ -165,6 +166,11 @@
   return annotationLayer;
 };
 
+GrAnnotationActionsInterface.prototype.disposeLayer = function(path) {
+  this._annotationLayers = this._annotationLayers
+      .filter(annotationLayer => annotationLayer._path !== path);
+};
+
 /**
  * Used to create an instance of the Annotation Layer interface.
  *
@@ -186,6 +192,7 @@
 
 /**
  * Register a listener for layer updates.
+ * Don't forget to removeListener when you stop using layer.
  *
  * @param {Function} fn The update handler function.
  *     Should accept as arguments the line numbers for the start and end of
@@ -195,6 +202,10 @@
   this._listeners.push(fn);
 };
 
+AnnotationLayer.prototype.removeListener = function(fn) {
+  this._listeners = this._listeners.filter(f => f != fn);
+};
+
 /**
  * Layer method to add annotations to a line.
  *
diff --git a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-js-api-interface-element.js b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-js-api-interface-element.js
index a98936f..43d7378 100644
--- a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-js-api-interface-element.js
+++ b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-js-api-interface-element.js
@@ -277,6 +277,17 @@
     return layers;
   }
 
+  disposeDiffLayers(path) {
+    for (const annotationApi of
+      this._getEventCallbacks(EventType.ANNOTATE_DIFF)) {
+      try {
+        annotationApi.disposeLayer(path);
+      } catch (err) {
+        console.error(err);
+      }
+    }
+  }
+
   /**
    * Retrieves coverage data possibly provided by a plugin.
    *