Adds diff traversal helper and make diff object a builder property

Adds a polymorphic method to GrDiffBuilder subclasses named
`_getNextContentOnSide` which gets the a content element by traversing
from its preceding content on the same side. This method is dramatically
faster than the query-based method when diff sections are large.

In preparation for the syntax highlighting change, the gr-diff-builder
is refactored to use a property for the diff object, rather than
accepting it as a parameter to the `render` function.

Tests are included for new functions.

Change-Id: Ifd0edb530a303de2626d55a691c3ba1eaed6167c
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-side-by-side.js b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-side-by-side.js
index 0d9ecbc..1cb8cc7 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-side-by-side.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-side-by-side.js
@@ -66,5 +66,17 @@
     }
   };
 
+  GrDiffBuilderSideBySide.prototype._getNextContentOnSide = function(
+      content, side) {
+    var tr = content.parentElement.parentElement;
+    var content;
+    while (tr = tr.nextSibling) {
+      content = tr.querySelector(
+          'td.content .contentText[data-side="' + side + '"]');
+      if (content) { return content; }
+    }
+    return null;
+  };
+
   window.GrDiffBuilderSideBySide = GrDiffBuilderSideBySide;
 })(window, GrDiffBuilder);
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-unified.js b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-unified.js
index d9c9b1b..960bf46 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-unified.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-unified.js
@@ -59,5 +59,19 @@
     return row;
   };
 
+  GrDiffBuilderUnified.prototype._getNextContentOnSide = function(
+      content, side) {
+    var tr = content.parentElement.parentElement;
+    var content;
+    while (tr = tr.nextSibling) {
+      if (tr.classList.contains('both') || (
+          (side === 'left' && tr.classList.contains('remove')) ||
+          (side === 'right' && tr.classList.contains('add')))) {
+        return tr.querySelector('.contentText');
+      }
+    }
+    return null;
+  };
+
   window.GrDiffBuilderUnified = GrDiffBuilderUnified;
 })(window, GrDiffBuilder);
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 0aa5c94..3cad9d9 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
@@ -55,6 +55,7 @@
          */
 
         properties: {
+          diff: Object,
           viewMode: String,
           comments: Object,
           isImageDiff: Boolean,
@@ -81,11 +82,11 @@
           ];
         },
 
-        render: function(diff, comments, prefs) {
+        render: function(comments, prefs) {
           // Stop the processor (if it's running).
           this.$.processor.cancel();
 
-          this._builder = this._getDiffBuilder(diff, comments, prefs);
+          this._builder = this._getDiffBuilder(this.diff, comments, prefs);
 
           this.$.processor.context = prefs.context;
           this.$.processor.keyLocations = this._getCommentLocations(comments);
@@ -93,7 +94,7 @@
           this._clearDiffContent();
 
           console.time('diff render');
-          this.$.processor.process(diff.content).then(function() {
+          return this.$.processor.process(this.diff.content).then(function() {
             if (this.isImageDiff) {
               this._builder.renderDiffImages();
             }
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 389d290..816e3a4 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
@@ -139,6 +139,7 @@
     var groups = this.getGroupsByLineRange(start, end, opt_side);
 
     groups.forEach(function(group) {
+      var content = null;
       group.lines.forEach(function(line) {
         if ((opt_side === 'left' && line.type === GrDiffLine.Type.ADD) ||
             (opt_side === 'right' && line.type === GrDiffLine.Type.REMOVE)) {
@@ -150,8 +151,12 @@
 
         if (out_lines) { out_lines.push(line); }
         if (out_elements) {
-          var content = this.getContentByLine(lineNumber, opt_side,
-              group.element);
+          if (content) {
+            content = this._getNextContentOnSide(content, opt_side);
+          } else {
+            content = this.getContentByLine(lineNumber, opt_side,
+                group.element);
+          }
           if (content) { out_elements.push(content); }
         }
       }.bind(this));
@@ -537,5 +542,16 @@
     this._renderContentByRange(start, end, side);
   };
 
+  /**
+   * Finds the next DIV.contentText element following the given element, and on
+   * the same side. Will only search within a group.
+   * @param {HTMLElement} content
+   * @param {String} side Either 'left' or 'right'
+   * @return {HTMLElement}
+   */
+  GrDiffBuilder.prototype._getNextContentOnSide = function(content, side) {
+    throw Error('Subclasses must implement _getNextContentOnSide');
+  };
+
   window.GrDiffBuilder = GrDiffBuilder;
 })(window, GrDiffGroup, GrDiffLine);
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 f3d24f7..753ddaa 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
@@ -459,7 +459,8 @@
           };
           return builder;
         });
-        element.render({content: content}, {left: [], right: []}, prefs);
+        element.diff = {content: content};
+        element.render({left: [], right: []}, prefs);
       });
 
       test('renderSection', function() {
@@ -494,25 +495,23 @@
       var element;
       var builder;
       var diff;
+      var prefs;
 
       setup(function(done) {
         element = fixture('mock-diff');
         diff = document.createElement('mock-diff-response').diffResponse;
+        element.diff = diff;
 
-        var prefs = {
+        prefs = {
           line_length: 80,
           show_tabs: true,
           tab_size: 4,
         };
 
-        function doneHandler() {
-          element.removeEventListener('render', doneHandler);
+        element.render({left: [], right: []}, prefs).then(function() {
           builder = element._builder;
           done();
-        }
-        element.addEventListener('render', doneHandler);
-
-        element.render(diff, {left: [], right: []}, prefs);
+        });
       });
 
       test('getContentByLine', function() {
@@ -566,6 +565,70 @@
 
         spy.restore();
       });
+
+      test('_getNextContentOnSide side-by-side left', function() {
+        var startElem = builder.getContentByLine(5, 'left',
+            element.$.diffTable);
+        var expectedStartString = diff.content[2].ab[0];
+        var expectedNextString = diff.content[2].ab[1];
+        assert.equal(startElem.textContent, expectedStartString);
+
+        var nextElem = builder._getNextContentOnSide(startElem,
+            'left');
+        assert.equal(nextElem.textContent, expectedNextString);
+      });
+
+      test('_getNextContentOnSide side-by-side right', function() {
+        var startElem = builder.getContentByLine(5, 'right',
+            element.$.diffTable);
+        var expectedStartString = diff.content[1].b[0];
+        var expectedNextString = diff.content[1].b[1];
+        assert.equal(startElem.textContent, expectedStartString);
+
+        var nextElem = builder._getNextContentOnSide(startElem,
+            'right');
+        assert.equal(nextElem.textContent, expectedNextString);
+      });
+
+      test('_getNextContentOnSide unified left', function(done) {
+        // Re-render as unified:
+        element.viewMode = 'UNIFIED_DIFF';
+        element.render({left: [], right: []}, prefs).then(function() {
+          builder = element._builder;
+
+          var startElem = builder.getContentByLine(5, 'left',
+              element.$.diffTable);
+          var expectedStartString = diff.content[2].ab[0];
+          var expectedNextString = diff.content[2].ab[1];
+          assert.equal(startElem.textContent, expectedStartString);
+
+          var nextElem = builder._getNextContentOnSide(startElem,
+              'left');
+          assert.equal(nextElem.textContent, expectedNextString);
+
+          done();
+        });
+      });
+
+      test('_getNextContentOnSide unified right', function(done) {
+        // Re-render as unified:
+        element.viewMode = 'UNIFIED_DIFF';
+        element.render({left: [], right: []}, prefs).then(function() {
+          builder = element._builder;
+
+          var startElem = builder.getContentByLine(5, 'right',
+              element.$.diffTable);
+          var expectedStartString = diff.content[1].b[0];
+          var expectedNextString = diff.content[1].b[1];
+          assert.equal(startElem.textContent, expectedStartString);
+
+          var nextElem = builder._getNextContentOnSide(startElem,
+              'right');
+          assert.equal(nextElem.textContent, expectedNextString);
+
+          done();
+        });
+      });
     });
   });
 </script>
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 2e7fd56..33322df 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.html
@@ -151,6 +151,7 @@
           <gr-diff-builder
               id="diffBuilder"
               comments="[[_comments]]"
+              diff="[[_diff]]"
               view-mode="[[viewMode]]"
               is-image-diff="[[isImageDiff]]"
               base-image="[[_baseImage]]"
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 83ad58a..b0c1731 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.js
@@ -336,7 +336,7 @@
     },
 
     _render: function() {
-      this.$.diffBuilder.render(this._diff, this._comments, this.prefs);
+      this.$.diffBuilder.render(this._comments, this.prefs);
     },
 
     _clearDiffContent: function() {