Lift gr-syntax-layer into gr-diff-host

This change decouples gr-diff-builder and gr-syntax-layer. gr-syntax-
layer will now live in gr-diff-host.
As a result, from the perspective of gr-diff/gr-diff-builder, plugin
layers and the syntax layer will now be part of the same list.

(This change fixes a time reporting regression in https://gerrit-review.googlesource.com/c/gerrit/+/239972.)

A future potential refactor could also lift gr-coverage-layer instances
in a similar fashion.

Change-Id: I47e91fdac00e17460aab9ca57e4569ade63c2eeb
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 32fa7e5..374368c 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.html
@@ -378,7 +378,7 @@
               line-wrapping="[[lineWrapping]]"
               is-image-diff="[[isImageDiff]]"
               base-image="[[baseImage]]"
-              plugin-layers="[[pluginLayers]]"
+              layers="[[layers]]"
               revision-image="[[revisionImage]]">
             <table
                 id="diffTable"
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 16c92b1..7606928 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.js
@@ -271,7 +271,7 @@
 
       /** Set by Polymer. */
       isAttached: Boolean,
-      pluginLayers: Array,
+      layers: Array,
     },
 
     behaviors: [
@@ -724,7 +724,7 @@
 
     _diffChanged(newValue) {
       if (newValue) {
-        this._diffLength = this.$.diffBuilder.getDiffLength();
+        this._diffLength = this.getDiffLength(newValue);
         this._debounceRenderDiffTable();
       }
     },
@@ -766,7 +766,11 @@
       this.$.diffBuilder.render(keyLocations, this._getBypassPrefs())
           .then(() => {
             this.dispatchEvent(
-                new CustomEvent('render', {bubbles: true, composed: true}));
+                new CustomEvent('render', {
+                  bubbles: true,
+                  composed: true,
+                  detail: {contentRendered: true},
+                }));
           });
     },
 
@@ -955,5 +959,23 @@
       if (loading || !warning) { return 'newlineWarning hidden'; }
       return 'newlineWarning';
     },
+
+    /**
+     * Get the approximate length of the diff as the sum of the maximum
+     * length of the chunks.
+     * @param {Object} diff object
+     * @return {number}
+     */
+    getDiffLength(diff) {
+      return diff.content.reduce((sum, sec) => {
+        if (sec.hasOwnProperty('ab')) {
+          return sum + sec.ab.length;
+        } else {
+          return sum + Math.max(
+              sec.hasOwnProperty('a') ? sec.a.length : 0,
+              sec.hasOwnProperty('b') ? sec.b.length : 0);
+        }
+      }, 0);
+    },
   });
 })();
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 15deaff..5366664 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
@@ -767,7 +767,7 @@
                   new CustomEvent('render', {bubbles: true, composed: true}));
             });
         const mock = document.createElement('mock-diff-response');
-        sandbox.stub(element.$.diffBuilder, 'getDiffLength').returns(10000);
+        sandbox.stub(element, 'getDiffLength').returns(10000);
         element.diff = mock.diffResponse;
         element.noRenderOnPrefsChange = true;
       });
@@ -1101,6 +1101,23 @@
           ));
       });
     });
+
+    test('getDiffLength', () => {
+      const diff = document.createElement('mock-diff-response').diffResponse;
+      assert.equal(element.getDiffLength(diff), 52);
+    });
+
+    test('`render` event has contentRendered field in detail', done => {
+      element = fixture('basic');
+      element.prefs = {};
+      renderStub = sandbox.stub(element.$.diffBuilder, 'render')
+            .returns(Promise.resolve());
+      element.addEventListener('render', event => {
+        assert.isTrue(event.detail.contentRendered);
+        done();
+      });
+      element._renderDiffTable();
+    });
   });
 
   a11ySuite('basic');