Merge "Fix event targeting in menu-editor"
diff --git a/.gitignore b/.gitignore
index 06a6e66..0e954ce 100644
--- a/.gitignore
+++ b/.gitignore
@@ -30,3 +30,4 @@
 /plugins/cookbook-plugin/
 /test_site
 /tools/format
+/.vscode
\ No newline at end of file
diff --git a/Documentation/pg-plugin-endpoints.txt b/Documentation/pg-plugin-endpoints.txt
index 8ae25fd..7c960dd 100644
--- a/Documentation/pg-plugin-endpoints.txt
+++ b/Documentation/pg-plugin-endpoints.txt
@@ -40,6 +40,16 @@
 view page, and it may take full page's width. Primary purpose is to enable
 plugins to display custom CI related information (build status, etc).
 
+* `change`
++
+current change displayed, an instance of
+link:rest-api-changes.html#change-info[ChangeInfo]
+
+* `revision`
++
+current revision displayed, an instance of
+link:rest-api-changes.html#revision-info[RevisionInfo]
+
 === change-metadata-item
 Extension point is located on the bottom of the change view left panel, under
 `Label Status` and `Links` sections. It's width is equal to the left panel's and
@@ -52,3 +62,8 @@
 +
 current change displayed, an instance of
 link:rest-api-changes.html#change-info[ChangeInfo]
+
+* `revision`
++
+current revision displayed, an instance of
+link:rest-api-changes.html#revision-info[RevisionInfo]
diff --git a/polygerrit-ui/app/elements/admin/gr-project/gr-project.js b/polygerrit-ui/app/elements/admin/gr-project/gr-project.js
index 0f14088..b54824b 100644
--- a/polygerrit-ui/app/elements/admin/gr-project/gr-project.js
+++ b/polygerrit-ui/app/elements/admin/gr-project/gr-project.js
@@ -165,7 +165,7 @@
     _formatBooleanSelect(item) {
       if (!item) { return; }
       let inheritLabel = 'Inherit';
-      if (item.inherited_value) {
+      if (!(item.inherited_value === undefined)) {
         inheritLabel = `Inherit (${item.inherited_value})`;
       }
       return [
diff --git a/polygerrit-ui/app/elements/admin/gr-project/gr-project_test.html b/polygerrit-ui/app/elements/admin/gr-project/gr-project_test.html
index 4decadb..ce7fe80 100644
--- a/polygerrit-ui/app/elements/admin/gr-project/gr-project_test.html
+++ b/polygerrit-ui/app/elements/admin/gr-project/gr-project_test.html
@@ -166,7 +166,7 @@
     });
 
     test('_formatBooleanSelect', () => {
-      let item = {inherited_value: 'true'};
+      let item = {inherited_value: true};
       assert.deepEqual(element._formatBooleanSelect(item), [
         {
           label: 'Inherit (true)',
@@ -181,6 +181,21 @@
         },
       ]);
 
+      item = {inherited_value: false};
+      assert.deepEqual(element._formatBooleanSelect(item), [
+        {
+          label: 'Inherit (false)',
+          value: 'INHERIT',
+        },
+        {
+          label: 'True',
+          value: 'TRUE',
+        }, {
+          label: 'False',
+          value: 'FALSE',
+        },
+      ]);
+
       // For items without inherited values
       item = {};
       assert.deepEqual(element._formatBooleanSelect(item), [
diff --git a/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.html b/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.html
index 8007993..481cd76 100644
--- a/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.html
+++ b/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.html
@@ -52,11 +52,6 @@
         /* px because don't have the same font size */
         margin-left: 12px;
       }
-      gr-button[data-action-key='submit'] {
-        --gr-button-background: #f67070;
-        --gr-button-color: #fff;
-        --gr-button-hover-background-color: #dc5151;
-      }
       #actionLoadingMessage {
         align-items: center;
         color: #777;
@@ -107,11 +102,6 @@
                 on-tap="_handleActionTap">[[action.label]]</gr-button>
           </template>
         </section>
-        <gr-button
-            class="reply"
-            secondary
-            disabled="[[replyDisabled]]"
-            on-tap="_handleReplyTap">[[replyButtonLabel]]</gr-button>
         <section id="secondaryActions"
             hidden$="[[_shouldHideActions(_topLevelActions.*, _loading)]]">
           <template
diff --git a/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.js b/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.js
index 27bd5e9..b2a6f0d 100644
--- a/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.js
+++ b/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.js
@@ -160,12 +160,6 @@
      * @event reload-change
      */
 
-     /**
-     * Fired when the reply button is tapped.
-     *
-     * @event reply-tap
-     */
-
     /**
      * Fired when an action is tapped.
      *
@@ -214,8 +208,6 @@
         type: Object,
         value() { return {}; },
       },
-      replyButtonLabel: String,
-      replyDisabled: Boolean,
 
       _loading: {
         type: Boolean,
@@ -729,11 +721,6 @@
       this._showActionDialog(this.$.confirmRevertDialog);
     },
 
-    _handleReplyTap(e) {
-      e.preventDefault();
-      this.dispatchEvent(new CustomEvent('reply-tap'));
-    },
-
     _handleActionTap(e) {
       e.preventDefault();
       const el = Polymer.dom(e).localTarget;
diff --git a/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions_test.html b/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions_test.html
index ea85d86..62b4626 100644
--- a/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions_test.html
+++ b/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions_test.html
@@ -194,7 +194,7 @@
         const buttonEls = Polymer.dom(element.root)
             .querySelectorAll('gr-button');
         const menuItems = element.$.moreActions.items;
-        assert.equal(buttonEls.length + menuItems.length, 7);
+        assert.equal(buttonEls.length + menuItems.length, 6);
         assert.isFalse(element.hidden);
         done();
       });
diff --git a/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.html b/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.html
index 936b284..caf2fa3 100644
--- a/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.html
+++ b/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.html
@@ -19,6 +19,7 @@
 <link rel="import" href="../../../styles/shared-styles.html">
 <link rel="import" href="../../core/gr-navigation/gr-navigation.html">
 <link rel="import" href="../../plugins/gr-endpoint-decorator/gr-endpoint-decorator.html">
+<link rel="import" href="../../plugins/gr-endpoint-param/gr-endpoint-param.html">
 <link rel="import" href="../../plugins/gr-external-style/gr-external-style.html">
 <link rel="import" href="../../shared/gr-account-chip/gr-account-chip.html">
 <link rel="import" href="../../shared/gr-account-link/gr-account-link.html">
@@ -330,6 +331,7 @@
       </section>
       <gr-endpoint-decorator name="change-metadata-item">
         <gr-endpoint-param name="change" value="[[change]]"></gr-endpoint-param>
+        <gr-endpoint-param name="revision" value="[[currentRevision]]"></gr-endpoint-param>
       </gr-endpoint-decorator>
     </gr-external-style>
     <gr-rest-api-interface id="restAPI"></gr-rest-api-interface>
diff --git a/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.js b/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.js
index db6c44c..00b4c2a 100644
--- a/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.js
+++ b/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.js
@@ -37,6 +37,8 @@
     properties: {
       /** @type {?} */
       change: Object,
+      /** @type {?} */
+      currentRevision: Object,
       commitInfo: Object,
       mutable: Boolean,
       /**
diff --git a/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata_test.html b/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata_test.html
index 522d34e..0b88f22 100644
--- a/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata_test.html
+++ b/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata_test.html
@@ -38,6 +38,9 @@
 
     setup(() => {
       sandbox = sinon.sandbox.create();
+      stub('gr-endpoint-decorator', {
+        _import: sandbox.stub().returns(Promise.resolve()),
+      });
       stub('gr-rest-api-interface', {
         getConfig() { return Promise.resolve({}); },
         getLoggedIn() { return Promise.resolve(false); },
@@ -615,5 +618,29 @@
         });
       });
     });
+
+    suite('plugin endpoints', () => {
+      test('endpoint params', done => {
+        element.change = {labels: {}};
+        element.currentRevision = {};
+        let hookEl;
+        let plugin;
+        Gerrit.install(
+            p => {
+              plugin = p;
+              plugin.hook('change-metadata-item').getLastAttached().then(
+                  el => hookEl = el);
+            },
+            '0.1',
+            'http://some/plugins/url.html');
+        Gerrit._setPluginsCount(0);
+        flush(() => {
+          assert.strictEqual(hookEl.plugin, plugin);
+          assert.strictEqual(hookEl.change, element.change);
+          assert.strictEqual(hookEl.revision, element.currentRevision);
+          done();
+        });
+      });
+    });
   });
 </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 3365098..56548de 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
@@ -15,7 +15,6 @@
 -->
 
 <link rel="import" href="../../../bower_components/polymer/polymer.html">
-
 <link rel="import" href="../../../behaviors/gr-patch-set-behavior/gr-patch-set-behavior.html">
 <link rel="import" href="../../../behaviors/keyboard-shortcut-behavior/keyboard-shortcut-behavior.html">
 <link rel="import" href="../../../behaviors/rest-client-behavior/rest-client-behavior.html">
@@ -25,6 +24,7 @@
 <link rel="import" href="../../diff/gr-diff-preferences/gr-diff-preferences.html">
 <link rel="import" href="../../edit/gr-edit-constants.html">
 <link rel="import" href="../../plugins/gr-endpoint-decorator/gr-endpoint-decorator.html">
+<link rel="import" href="../../plugins/gr-endpoint-param/gr-endpoint-param.html">
 <link rel="import" href="../../shared/gr-account-link/gr-account-link.html">
 <link rel="import" href="../../shared/gr-button/gr-button.html">
 <link rel="import" href="../../shared/gr-change-star/gr-change-star.html">
@@ -87,6 +87,9 @@
       .header-title .headerSubject {
         font-family: var(--font-family-bold);
       }
+      .replyContainer {
+        margin-bottom: 1em;
+      }
       gr-change-star {
         margin-right: .25em;
         vertical-align: -.425em;
@@ -133,6 +136,10 @@
       }
       .editCommitMessage {
         margin-top: 1em;
+        --gr-button: {
+          padding-left: 0;
+          padding-right: 0;
+        }
       }
       .changeStatuses,
       .commitActions {
@@ -343,14 +350,14 @@
               edit-loaded="[[_editLoaded]]"
               edit-based-on-current-patch-set="[[hasEditBasedOnCurrentPatchSet(_allPatchSets)]]"
               on-reload-change="_handleReloadChange"
-              on-download-tap="_handleOpenDownloadDialog"
-              on-reply-tap="_handleReplyTap"></gr-change-actions>
+              on-download-tap="_handleOpenDownloadDialog"></gr-change-actions>
         </div><!-- end commit actions -->
       </div><!-- end header -->
       <section class="changeInfo">
         <div class="changeInfo-column changeMetadata hideOnMobileOverlay">
           <gr-change-metadata
               change="{{_change}}"
+              revision="[[_currentRevision]]"
               commit-info="[[_commitInfo]]"
               server-config="[[_serverConfig]]"
               mutable="[[_loggedIn]]"
@@ -365,6 +372,14 @@
           <hr class="mobile">
           <div id="commitAndRelated" class="hideOnMobileOverlay">
             <div class="commitContainer">
+              <div class="replyContainer">
+                  <gr-button
+                      id="replyBtn"
+                      class="reply"
+                      secondary
+                      disabled="[[_replyDisabled]]"
+                      on-tap="_handleReplyTap">[[_replyButtonLabel]]</gr-button>
+              </div>
               <div
                   id="commitMessage"
                   class$="commitMessage [[_computeCommitClass(_commitCollapsed, _latestCommitMessage)]]">
@@ -472,6 +487,10 @@
             on-reload-drafts="_reloadDraftsWithCallback"></gr-file-list>
       </section>
       <gr-endpoint-decorator name="change-view-integration">
+        <gr-endpoint-param name="change" value="[[_change]]">
+        </gr-endpoint-param>
+        <gr-endpoint-param name="revision" value="[[_currentRevision]]">
+        </gr-endpoint-param>
       </gr-endpoint-decorator>
       <gr-messages-list id="messageList"
           class="hideOnMobileOverlay"
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 cd56752..145c1f8 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
@@ -166,6 +166,7 @@
         type: Boolean,
         value: true,
       },
+      _currentRevision: Object,
       _currentRevisionActions: Object,
       _allPatchSets: {
         type: Array,
@@ -983,6 +984,7 @@
                 parseInt(lineHeight.slice(0, lineHeight.length - 2), 10);
 
             this._change = change;
+            this._currentRevision = currentRevision;
             if (!this._patchRange || !this._patchRange.patchNum ||
                 this.patchNumEquals(this._patchRange.patchNum,
                     currentRevision._number)) {
diff --git a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_test.html b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_test.html
index d8004e5..ccdc50e 100644
--- a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_test.html
+++ b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_test.html
@@ -49,6 +49,11 @@
 
     setup(() => {
       sandbox = sinon.sandbox.create();
+      stub('gr-endpoint-decorator', {
+        _import: sandbox.stub().returns(Promise.resolve()),
+      });
+      // Since _endpoints are global, must reset state.
+      Gerrit._endpoints = new GrPluginEndpoints();
       navigateToChangeStub = sandbox.stub(Gerrit.Nav, 'navigateToChange');
       stub('gr-rest-api-interface', {
         getConfig() { return Promise.resolve({test: 'config'}); },
@@ -840,14 +845,16 @@
       });
     });
 
-    test('_openReplyDialog called with `ANY` when coming from tap event', () => {
-      const openStub = sandbox.stub(element, '_openReplyDialog');
-      element.$.actions.fire('reply-tap');
-      assert(openStub.lastCall.calledWithExactly(
-          element.$.replyDialog.FocusTarget.ANY),
-          '_openReplyDialog should have been passed ANY');
-      assert.equal(openStub.callCount, 1);
-    });
+    test('_openReplyDialog called with `ANY` when coming from tap event',
+        () => {
+          const openStub = sandbox.stub(element, '_openReplyDialog');
+          element._serverConfig = {};
+          MockInteractions.tap(element.$.replyBtn);
+          assert(openStub.lastCall.calledWithExactly(
+              element.$.replyDialog.FocusTarget.ANY),
+              '_openReplyDialog should have been passed ANY');
+          assert.equal(openStub.callCount, 1);
+        });
 
     test('_openReplyDialog called with `BODY` when coming from message reply' +
         'event', () => {
@@ -1371,5 +1378,30 @@
       assert.equal(Gerrit.Nav.getEditUrlForDiff.lastCall.args[1], 'foo');
       assert.isTrue(Gerrit.Nav.navigateToRelativeUrl.called);
     });
+
+    suite('plugin endpoints', () => {
+      test('endpoint params', done => {
+        element._change = {labels: {}};
+        element._currentRevision = {};
+        let hookEl;
+        let plugin;
+        Gerrit.install(
+            p => {
+              plugin = p;
+              plugin.hook('change-view-integration').getLastAttached().then(
+                  el => hookEl = el);
+            },
+            '0.1',
+            'http://some/plugins/url.html');
+        Gerrit._setPluginsCount(0);
+        flush(() => {
+          assert.strictEqual(hookEl.plugin, plugin);
+          assert.strictEqual(hookEl.change, element._change);
+          assert.strictEqual(hookEl.revision, element._currentRevision);
+          done();
+        });
+      });
+    });
   });
+
 </script>
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-highlight/gr-diff-highlight.js b/polygerrit-ui/app/elements/diff/gr-diff-highlight/gr-diff-highlight.js
index 2490509..03380e4 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-highlight/gr-diff-highlight.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff-highlight/gr-diff-highlight.js
@@ -251,6 +251,22 @@
       };
     },
 
+    /**
+     * The only line in which add a comment tooltip is cut off is the first
+     * line. Even if there is a collapsed section, The first visible line is
+     * in the position where the second line would have been, if not for the
+     * collapsed section, so don't need to worry about this case for
+     * positioning the tooltip.
+     */
+    _positionActionBox(actionBox, startLine, range) {
+      if (startLine > 1) {
+        actionBox.placeAbove(range);
+        return;
+      }
+      actionBox.positionBelow = true;
+      actionBox.placeBelow(range);
+    },
+
     _handleSelection() {
       const normalizedRange = this._getNormalizedRange();
       if (!normalizedRange) {
@@ -285,17 +301,18 @@
       };
       actionBox.side = start.side;
       if (start.line === end.line) {
-        actionBox.placeAbove(domRange);
+        this._positionActionBox(actionBox, start.line, domRange);
       } else if (start.node instanceof Text) {
         if (start.column) {
-          actionBox.placeAbove(start.node.splitText(start.column));
+          this._positionActionBox(actionBox, start.line,
+              start.node.splitText(start.column));
         }
         start.node.parentElement.normalize(); // Undo splitText from above.
       } else if (start.node.classList.contains('content') &&
-                 start.node.firstChild) {
-        actionBox.placeAbove(start.node.firstChild);
+          start.node.firstChild) {
+        this._positionActionBox(actionBox, start.line, start.node.firstChild);
       } else {
-        actionBox.placeAbove(start.node);
+        this._positionActionBox(actionBox, start.line, start.node);
       }
     },
 
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-highlight/gr-diff-highlight_test.html b/polygerrit-ui/app/elements/diff/gr-diff-highlight/gr-diff-highlight_test.html
index b63b9a4..4bbf12b 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-highlight/gr-diff-highlight_test.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff-highlight/gr-diff-highlight_test.html
@@ -38,6 +38,27 @@
       <table id="diffTable">
 
         <tbody class="section both">
+           <tr class="diff-row side-by-side" left-type="both" right-type="both">
+            <td class="left lineNum" data-value="1"></td>
+            <td class="content both"><div class="contentText">[1] Nam cum ad me in Cumanum salutandi causa uterque venisset,</div></td>
+            <td class="right lineNum" data-value="1"></td>
+            <td class="content both"><div class="contentText">[1] Nam cum ad me in Cumanum salutandi causa uterque</div></td>
+          </tr>
+        </tbody>
+
+        <tbody class="section delta">
+          <tr class="diff-row side-by-side" left-type="remove" right-type="add">
+            <td class="left lineNum" data-value="2"></td>
+            <!-- Next tag is formatted to eliminate zero-length text nodes. -->
+            <td class="content remove"><div class="contentText">na💢ti <hl class="foo">te, inquit</hl>, sumus <hl class="bar">aliquando</hl> otiosum, <hl>certe</hl> a <hl><span class="tab-indicator" style="tab-size:8;">	</span></hl>udiam, <hl>quid</hl> sit, <span class="tab-indicator" style="tab-size:8;">	</span>quod <hl>Epicurum</hl></div></td>
+            <td class="right lineNum" data-value="2"></td>
+            <!-- Next tag is formatted to eliminate zero-length text nodes. -->
+            <td class="content add"><div class="contentText">nacti , <hl>,</hl> sumus <hl><span class="tab-indicator" style="tab-size:8;">	</span></hl> otiosum,  <span class="tab-indicator" style="tab-size:8;">	</span> audiam,  sit, quod</div></td>
+          </tr>
+        </tbody>
+
+
+        <tbody class="section both">
           <tr class="diff-row side-by-side" left-type="both" right-type="both">
             <td class="left lineNum" data-value="138"></td>
             <td class="content both"><div class="contentText">[14] Nam cum ad me in Cumanum salutandi causa uterque venisset,</div></td>
@@ -253,6 +274,7 @@
         contentStubs = [];
         stub('gr-selection-action-box', {
           placeAbove: sandbox.stub(),
+          placeBelow: sandbox.stub(),
         });
         diff = element.querySelector('#diffTable');
         builder = {
@@ -270,9 +292,29 @@
         window.getSelection().removeAllRanges();
       });
 
+      test('single first line', () => {
+        const content = stubContent(1, 'right');
+        sandbox.spy(element, '_positionActionBox');
+        emulateSelection(content.firstChild, 5, content.firstChild, 12);
+        const actionBox = element.$$('gr-selection-action-box');
+        assert.isTrue(actionBox.positionBelow);
+      });
+
+      test('multiline starting on first line', () => {
+        const startContent = stubContent(1, 'right');
+        const endContent = stubContent(2, 'right');
+        sandbox.spy(element, '_positionActionBox');
+        emulateSelection(
+            startContent.firstChild, 10, endContent.lastChild, 7);
+        const actionBox = element.$$('gr-selection-action-box');
+        assert.isTrue(actionBox.positionBelow);
+      });
+
       test('single line', () => {
         const content = stubContent(138, 'left');
+        sandbox.spy(element, '_positionActionBox');
         emulateSelection(content.firstChild, 5, content.firstChild, 12);
+        const actionBox = element.$$('gr-selection-action-box');
         assert.isTrue(element.isRangeSelected());
         assert.deepEqual(getActionRange(), {
           startLine: 138,
@@ -281,14 +323,18 @@
           endChar: 12,
         });
         assert.equal(getActionSide(), 'left');
+        assert.notOk(actionBox.positionBelow);
       });
 
       test('multiline', () => {
         const startContent = stubContent(119, 'right');
         const endContent = stubContent(120, 'right');
+        sandbox.spy(element, '_positionActionBox');
         emulateSelection(
             startContent.firstChild, 10, endContent.lastChild, 7);
         assert.isTrue(element.isRangeSelected());
+        const actionBox = element.$$('gr-selection-action-box');
+
         assert.deepEqual(getActionRange(), {
           startLine: 119,
           startChar: 10,
@@ -296,6 +342,7 @@
           endChar: 36,
         });
         assert.equal(getActionSide(), 'right');
+        assert.notOk(actionBox.positionBelow);
       });
 
       test('multiple ranges aka firefox implementation', () => {
diff --git a/polygerrit-ui/app/elements/diff/gr-selection-action-box/gr-selection-action-box.html b/polygerrit-ui/app/elements/diff/gr-selection-action-box/gr-selection-action-box.html
index 47db5f0..3993b86 100644
--- a/polygerrit-ui/app/elements/diff/gr-selection-action-box/gr-selection-action-box.html
+++ b/polygerrit-ui/app/elements/diff/gr-selection-action-box/gr-selection-action-box.html
@@ -17,34 +17,24 @@
 <link rel="import" href="../../../bower_components/polymer/polymer.html">
 <link rel="import" href="../../../behaviors/keyboard-shortcut-behavior/keyboard-shortcut-behavior.html">
 <link rel="import" href="../../../styles/shared-styles.html">
+<link rel="import" href="../../shared/gr-tooltip/gr-tooltip.html">
 
 <dom-module id="gr-selection-action-box">
   <template>
     <style include="shared-styles">
       :host {
-        --gr-arrow-size: .65em;
-
-        background-color: rgba(22, 22, 22, .9);
-        border-radius: 3px;
-        color: #fff;
         cursor: pointer;
         font-family: var(--font-family);
-        padding: .5em .75em;
         position: absolute;
         white-space: nowrap;
       }
-      .arrow {
-        border: var(--gr-arrow-size) solid transparent;
-        border-top: var(--gr-arrow-size) solid rgba(22, 22, 22, 0.9);
-        height: 0;
-        left: calc(50% - var(--gr-arrow-size));
-        margin-top: .5em;
-        position: absolute;
-        width: 0;
+      #tooltip {
+        --tooltip-background-color: rgba(22, 22, 22, .9);
       }
     </style>
-    Press <strong>c</strong> to comment.
-    <div class="arrow"></div>
+    <gr-tooltip id="tooltip"
+        text="press c to comment"
+        position-below="[[positionBelow]]"></gr-tooltip>
   </template>
   <script src="gr-selection-action-box.js"></script>
 </dom-module>
diff --git a/polygerrit-ui/app/elements/diff/gr-selection-action-box/gr-selection-action-box.js b/polygerrit-ui/app/elements/diff/gr-selection-action-box/gr-selection-action-box.js
index c228235..61a6eb2 100644
--- a/polygerrit-ui/app/elements/diff/gr-selection-action-box/gr-selection-action-box.js
+++ b/polygerrit-ui/app/elements/diff/gr-selection-action-box/gr-selection-action-box.js
@@ -37,6 +37,7 @@
           endChar: NaN,
         },
       },
+      positionBelow: Boolean,
       side: {
         type: String,
         value: '',
@@ -58,7 +59,7 @@
     placeAbove(el) {
       Polymer.dom.flush();
       const rect = this._getTargetBoundingRect(el);
-      const boxRect = this.getBoundingClientRect();
+      const boxRect = this.$.tooltip.getBoundingClientRect();
       const parentRect = this.parentElement.getBoundingClientRect();
       this.style.top =
           rect.top - parentRect.top - boxRect.height - 6 + 'px';
@@ -66,6 +67,17 @@
           rect.left - parentRect.left + (rect.width - boxRect.width) / 2 + 'px';
     },
 
+    placeBelow(el) {
+      Polymer.dom.flush();
+      const rect = this._getTargetBoundingRect(el);
+      const boxRect = this.$.tooltip.getBoundingClientRect();
+      const parentRect = this.parentElement.getBoundingClientRect();
+      this.style.top =
+          rect.top - parentRect.top + boxRect.height - 6 + 'px';
+      this.style.left =
+          rect.left - parentRect.left + (rect.width - boxRect.width) / 2 + 'px';
+    },
+
     _getTargetBoundingRect(el) {
       let rect;
       if (el instanceof Text) {
diff --git a/polygerrit-ui/app/elements/diff/gr-selection-action-box/gr-selection-action-box_test.html b/polygerrit-ui/app/elements/diff/gr-selection-action-box/gr-selection-action-box_test.html
index 8c70772..7fc634c 100644
--- a/polygerrit-ui/app/elements/diff/gr-selection-action-box/gr-selection-action-box_test.html
+++ b/polygerrit-ui/app/elements/diff/gr-selection-action-box/gr-selection-action-box_test.html
@@ -38,15 +38,17 @@
   suite('gr-selection-action-box', () => {
     let container;
     let element;
+    let sandbox;
 
     setup(() => {
       container = fixture('basic');
       element = container.querySelector('gr-selection-action-box');
-      sinon.stub(element, 'fire');
+      sandbox = sinon.sandbox.create();
+      sandbox.stub(element, 'fire');
     });
 
     teardown(() => {
-      element.fire.restore();
+      sandbox.restore();
     });
 
     test('ignores regular keys', () => {
@@ -65,10 +67,10 @@
       setup(() => {
         e = {
           button: 0,
-          preventDefault: sinon.stub(),
-          stopPropagation: sinon.stub(),
+          preventDefault: sandbox.stub(),
+          stopPropagation: sandbox.stub(),
         };
-        sinon.stub(element, '_fireCreateComment');
+        sandbox.stub(element, '_fireCreateComment');
       });
 
       test('event handled if main button', () => {
@@ -107,20 +109,14 @@
 
       setup(() => {
         target = container.querySelector('.target');
-        sinon.stub(container, 'getBoundingClientRect').returns(
+        sandbox.stub(container, 'getBoundingClientRect').returns(
             {top: 1, bottom: 2, left: 3, right: 4, width: 50, height: 6});
-        sinon.stub(element, '_getTargetBoundingRect').returns(
+        sandbox.stub(element, '_getTargetBoundingRect').returns(
             {top: 42, bottom: 20, left: 30, right: 40, width: 100, height: 60});
-        sinon.stub(element, 'getBoundingClientRect').returns(
+        sandbox.stub(element.$.tooltip, 'getBoundingClientRect').returns(
             {width: 10, height: 10});
       });
 
-      teardown(() => {
-        element.getBoundingClientRect.restore();
-        container.getBoundingClientRect.restore();
-        element._getTargetBoundingRect.restore();
-      });
-
       test('placeAbove for Element argument', () => {
         element.placeAbove(target);
         assert.equal(element.style.top, '25px');
@@ -133,13 +129,24 @@
         assert.equal(element.style.left, '72px');
       });
 
+      test('placeBelow for Element argument', () => {
+        element.placeBelow(target);
+        assert.equal(element.style.top, '45px');
+        assert.equal(element.style.left, '72px');
+      });
+
+      test('placeBelow for Text Node argument', () => {
+        element.placeBelow(target.firstChild);
+        assert.equal(element.style.top, '45px');
+        assert.equal(element.style.left, '72px');
+      });
+
       test('uses document.createRange', () => {
-        sinon.spy(document, 'createRange');
+        sandbox.spy(document, 'createRange');
         element._getTargetBoundingRect.restore();
-        sinon.spy(element, '_getTargetBoundingRect');
+        sandbox.spy(element, '_getTargetBoundingRect');
         element.placeAbove(target.firstChild);
         assert.isTrue(document.createRange.called);
-        document.createRange.restore();
       });
     });
   });
diff --git a/polygerrit-ui/app/elements/shared/gr-button/gr-button.html b/polygerrit-ui/app/elements/shared/gr-button/gr-button.html
index 6918f30..a56394e 100644
--- a/polygerrit-ui/app/elements/shared/gr-button/gr-button.html
+++ b/polygerrit-ui/app/elements/shared/gr-button/gr-button.html
@@ -75,7 +75,7 @@
       /* styles for raised buttons specifically*/
       :host([primary]) paper-button[raised],
       :host([secondary]) paper-button[raised] {
-        background-color: var(--gr-button-background, --color-link);
+        background-color: var(--color-link);
         color: #fff;
       }
       :host([primary]) paper-button[raised]:hover,
diff --git a/polygerrit-ui/app/elements/shared/gr-tooltip/gr-tooltip.html b/polygerrit-ui/app/elements/shared/gr-tooltip/gr-tooltip.html
index c34bc69..1744d28 100644
--- a/polygerrit-ui/app/elements/shared/gr-tooltip/gr-tooltip.html
+++ b/polygerrit-ui/app/elements/shared/gr-tooltip/gr-tooltip.html
@@ -24,7 +24,7 @@
         --gr-tooltip-arrow-size: .5em;
         --gr-tooltip-arrow-center-offset: 0;
 
-        background-color: #333;
+        background-color: var(--tooltip-background-color, #333);
         box-shadow: 0 1px 3px rgba(0, 0, 0, .3);
         color: #fff;
         font-size: .75rem;
@@ -52,11 +52,11 @@
         width: 0;
       }
       .arrowPositionAbove {
-        border-top: var(--gr-tooltip-arrow-size) solid #333;
+        border-top: var(--gr-tooltip-arrow-size) solid var(--tooltip-background-color, #333);
         bottom: -var(--gr-tooltip-arrow-size);
       }
       .arrowPositionBelow {
-        border-bottom: var(--gr-tooltip-arrow-size) solid #333;
+        border-bottom: var(--gr-tooltip-arrow-size) solid var(--tooltip-background-color, #333);
         top: -var(--gr-tooltip-arrow-size);
       }
     </style>