Merge changes Ie54178b7,I6fda4cce,I2f82060e,I9f95d6fc,I1bbdda5a

* changes:
  Add Gerrit.getLoggedIn to JS API
  Add change view reply dialog JS interface
  Add change view actions JS interface
  Add experimental labelchange event in JS API
  Add optional version parameter to JS API
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 d2026f6..b741784 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
@@ -59,7 +59,7 @@
       }
     </style>
     <div>
-      <section hidden$="[[!_keyCount(actions)]]" hidden>
+      <section hidden$="[[!_actionCount(actions.*, _additionalActions.*)]]">
         <div class="groupLabel">Change</div>
         <template is="dom-repeat" items="[[_changeActionValues]]" as="action">
           <gr-button title$="[[action.title]]"
@@ -72,7 +72,7 @@
               on-tap="_handleActionTap"></gr-button>
         </template>
       </section>
-      <section hidden$="[[!_keyCount(_revisionActions)]]" hidden>
+      <section hidden$="[[!_actionCount(_revisionActions.*, _additionalActions.*)]]">
         <div class="groupLabel">Revision</div>
         <template is="dom-repeat" items="[[_revisionActionValues]]" as="action">
           <gr-button title$="[[action.title]]"
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 d195241..9783831 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
@@ -59,7 +59,10 @@
      */
 
     properties: {
-      actions: Object,
+      actions: {
+        type: Object,
+        value: function() { return {}; },
+      },
       primaryActionKeys: {
         type: Array,
         value: function() {
@@ -77,7 +80,10 @@
         type: Boolean,
         value: true,
       },
-      _revisionActions: Object,
+      _revisionActions: {
+        type: Object,
+        value: function() { return {}; },
+      },
       _revisionActionValues: {
         type: Array,
         computed: '_computeRevisionActionValues(_revisionActions.*, ' +
@@ -103,7 +109,7 @@
     ],
 
     observers: [
-      '_actionsChanged(actions, _revisionActions, _additionalActions)',
+      '_actionsChanged(actions.*, _revisionActions.*, _additionalActions.*)',
     ],
 
     ready: function() {
@@ -129,7 +135,7 @@
       }.bind(this));
     },
 
-    addActionButton: function(key, type, label) {
+    addActionButton: function(type, label) {
       if (type !== ActionType.CHANGE && type !== ActionType.REVISION) {
         throw Error('Invalid action type: ' + type);
       }
@@ -137,39 +143,59 @@
         enabled: true,
         label: label,
         __type: type,
-        __key: ADDITIONAL_ACTION_KEY_PREFIX + key + Math.random().toString(36),
+        __key: ADDITIONAL_ACTION_KEY_PREFIX + Math.random().toString(36),
       };
       this.push('_additionalActions', action);
       return action.__key;
     },
 
     removeActionButton: function(key) {
-      var idx = -1;
-      for (var i = 0; i < this._additionalActions.length; i++) {
-        if (this._additionalActions[i].__key === key) {
-          idx = i;
-          break;
-        }
-      }
+      var idx = this._indexOfActionButtonWithKey(key);
       if (idx === -1) {
-        console.error('Could not find action button with key:', key);
+        return;
       }
       this.splice('_additionalActions', idx, 1);
     },
 
+    setActionButtonProp: function(key, prop, value) {
+      this.set([
+        '_additionalActions',
+        this._indexOfActionButtonWithKey(key),
+        prop,
+      ], value);
+    },
+
+    _indexOfActionButtonWithKey: function(key) {
+      for (var i = 0; i < this._additionalActions.length; i++) {
+        if (this._additionalActions[i].__key === key) {
+          return i;
+        }
+      }
+      return -1;
+    },
+
     _getRevisionActions: function() {
       return this.$.restAPI.getChangeRevisionActions(this.changeNum,
           this.patchNum);
     },
 
-    _keyCount: function(obj) {
-      return Object.keys(obj).length;
+    _actionCount: function(actionsChangeRecord, additionalActionsChangeRecord) {
+      var additionalActions = (additionalActionsChangeRecord &&
+          additionalActionsChangeRecord.base) || [];
+      return this._keyCount(actionsChangeRecord) + additionalActions.length;
     },
 
-    _actionsChanged: function(actions, revisionActions, additionalActions) {
-      this.hidden = this._keyCount(actions) === 0 &&
-          this._keyCount(revisionActions) === 0 &&
-              this._keyCount(additionalActions) === 0;
+    _keyCount: function(changeRecord) {
+      return Object.keys((changeRecord && changeRecord.base) || {}).length;
+    },
+
+    _actionsChanged: function(actionsChangeRecord, revisionActionsChangeRecord,
+        additionalActionsChangeRecord) {
+      var additionalActions = (additionalActionsChangeRecord &&
+          additionalActionsChangeRecord.base) || [];
+      this.hidden = this._keyCount(actionsChangeRecord) === 0 &&
+          this._keyCount(revisionActionsChangeRecord) === 0 &&
+              additionalActions.length === 0;
     },
 
     _getValuesFor: function(obj) {
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 fe04843..462b01b 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
@@ -203,8 +203,7 @@
     test('custom actions', function(done) {
       // Add a button with the same key as a server-based one to ensure
       // collisions are taken care of.
-      var key = element.addActionButton('submit', element.ActionType.REVISION,
-          'Bork!');
+      var key = element.addActionButton(element.ActionType.REVISION, 'Bork!');
       element.addEventListener(key + '-tap', function(e) {
         assert.equal(e.detail.node.getAttribute('data-action-key'), key);
         element.removeActionButton(key);
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 6af4493..f19d528 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
@@ -93,6 +93,10 @@
       Gerrit.RESTClientBehavior,
     ],
 
+    observers: [
+      '_labelsChanged(_change.labels.*)',
+    ],
+
     ready: function() {
       this._headerEl = this.$$('.header');
     },
@@ -485,6 +489,13 @@
       }
     },
 
+    _labelsChanged: function(changeRecord) {
+      if (!changeRecord) { return; }
+      this.$.jsAPI.handleEvent(this.$.jsAPI.EventType.LABEL_CHANGE, {
+        change: this._change,
+      });
+    },
+
     _openReplyDialog: function() {
       this.$.replyOverlay.open().then(function() {
         this.$.replyOverlay.setFocusStops(this.$.replyDialog.getFocusStops());
diff --git a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.html b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.html
index a364c79..556691a 100644
--- a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.html
+++ b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.html
@@ -19,6 +19,7 @@
 <link rel="import" href="../../../bower_components/iron-selector/iron-selector.html">
 <link rel="import" href="../../../behaviors/rest-client-behavior.html">
 <link rel="import" href="../../shared/gr-button/gr-button.html">
+<link rel="import" href="../../shared/gr-js-api-interface/gr-js-api-interface.html">
 <link rel="import" href="../../shared/gr-rest-api-interface/gr-rest-api-interface.html">
 
 <dom-module id="gr-reply-dialog">
@@ -154,6 +155,7 @@
             on-tap="_cancelTapHandler">Cancel</gr-button>
       </section>
     </div>
+    <gr-js-api-interface id="jsAPI"></gr-js-api-interface>
     <gr-rest-api-interface id="restAPI"></gr-rest-api-interface>
   </template>
   <script src="gr-reply-dialog.js"></script>
diff --git a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.js b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.js
index a702899..0ee779e 100644
--- a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.js
+++ b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.js
@@ -59,6 +59,10 @@
       }.bind(this));
     },
 
+    ready: function() {
+      this.$.jsAPI.addElement(this.$.jsAPI.Element.REPLY_DIALOG, this);
+    },
+
     focus: function() {
       this.async(function() {
         this.$.textarea.textarea.focus();
@@ -72,6 +76,48 @@
       };
     },
 
+    setLabelValue: function(label, value) {
+      var selectorEl = this.$$('iron-selector[data-label="' + label + '"]');
+      // The selector may not be present if it’s not at the latest patch set.
+      if (!selectorEl) { return; }
+      var item = selectorEl.$$('gr-button[data-value="' + value + '"]');
+      if (!item) { return; }
+      selectorEl.selectIndex(selectorEl.indexOf(item));
+    },
+
+    send: function() {
+      var obj = {
+        drafts: 'PUBLISH_ALL_REVISIONS',
+        labels: {},
+      };
+      for (var label in this.permittedLabels) {
+        if (!this.permittedLabels.hasOwnProperty(label)) { continue; }
+
+        var selectorEl = this.$$('iron-selector[data-label="' + label + '"]');
+
+        // The selector may not be present if it’s not at the latest patch set.
+        if (!selectorEl) { continue; }
+
+        var selectedVal = selectorEl.selectedItem.getAttribute('data-value');
+        selectedVal = parseInt(selectedVal, 10);
+        obj.labels[label] = selectedVal;
+      }
+      if (this.draft != null) {
+        obj.message = this.draft;
+      }
+      this.disabled = true;
+      return this._saveReview(obj).then(function(response) {
+        this.disabled = false;
+        if (!response.ok) { return response; }
+
+        this.draft = '';
+        this.fire('send', null, {bubbles: false});
+      }.bind(this)).catch(function(err) {
+        this.disabled = false;
+        throw err;
+      }.bind(this));
+    },
+
     _computeShowLabels: function(patchNum, revisions) {
       var num = parseInt(patchNum, 10);
       for (var rev in revisions) {
@@ -147,34 +193,7 @@
 
     _sendTapHandler: function(e) {
       e.preventDefault();
-      var obj = {
-        drafts: 'PUBLISH_ALL_REVISIONS',
-        labels: {},
-      };
-      for (var label in this.permittedLabels) {
-        var selectorEl = this.$$('iron-selector[data-label="' + label + '"]');
-
-        // The selector may not be present if it’s not at the latest patch set.
-        if (!selectorEl) { continue; }
-
-        var selectedVal = selectorEl.selectedItem.getAttribute('data-value');
-        selectedVal = parseInt(selectedVal, 10);
-        obj.labels[label] = selectedVal;
-      }
-      if (this.draft != null) {
-        obj.message = this.draft;
-      }
-      this.disabled = true;
-      this._saveReview(obj).then(function(response) {
-        this.disabled = false;
-        if (!response.ok) { return response; }
-
-        this.draft = '';
-        this.fire('send', null, {bubbles: false});
-      }.bind(this)).catch(function(err) {
-        this.disabled = false;
-        throw err;
-      }.bind(this));
+      this.send();
     },
 
     _saveReview: function(review) {
diff --git a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-change-actions-js-api.js b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-change-actions-js-api.js
new file mode 100644
index 0000000..f7c337b
--- /dev/null
+++ b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-change-actions-js-api.js
@@ -0,0 +1,62 @@
+// Copyright (C) 2016 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+(function(window) {
+  'use strict';
+
+  function GrChangeActionsInterface(el) {
+    this._el = el;
+    this.RevisionActions = el.RevisionActions;
+    this.ChangeActions = el.ChangeActions;
+    this.ActionType = el.ActionType;
+  }
+
+  GrChangeActionsInterface.prototype.addPrimaryActionKey = function(key) {
+    if (this._el.primaryActionKeys.indexOf(key) !== -1) { return; }
+
+    this._el.push('primaryActionKeys', key);
+  };
+
+  GrChangeActionsInterface.prototype.removePrimaryActionKey = function(key) {
+    this._el.primaryActionKeys = this._el.primaryActionKeys.filter(function(k) {
+      return k !== key;
+    });
+  };
+
+  GrChangeActionsInterface.prototype.add = function(type, label) {
+    return this._el.addActionButton(type, label);
+  };
+
+  GrChangeActionsInterface.prototype.remove = function(key) {
+    return this._el.removeActionButton(key);
+  };
+
+  GrChangeActionsInterface.prototype.addTapListener = function(key, handler) {
+    this._el.addEventListener(key + '-tap', handler);
+  };
+
+  GrChangeActionsInterface.prototype.removeTapListener = function(key,
+      handler) {
+    this._el.removeEventListener(key + '-tap', handler);
+  };
+
+  GrChangeActionsInterface.prototype.setLabel = function(key, text) {
+    this._el.setActionButtonProp(key, 'label', text);
+  };
+
+  GrChangeActionsInterface.prototype.setEnabled = function(key, enabled) {
+    this._el.setActionButtonProp(key, 'enabled', enabled);
+  };
+
+  window.GrChangeActionsInterface = GrChangeActionsInterface;
+})(window);
diff --git a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-change-actions-js-api_test.html b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-change-actions-js-api_test.html
new file mode 100644
index 0000000..3030870
--- /dev/null
+++ b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-change-actions-js-api_test.html
@@ -0,0 +1,123 @@
+<!DOCTYPE html>
+<!--
+Copyright (C) 2016 The Android Open Source Project
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+
+<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
+<title>gr-change-actions-js-api</title>
+
+<script src="../../../bower_components/webcomponentsjs/webcomponents.min.js"></script>
+<script src="../../../bower_components/web-component-tester/browser.js"></script>
+
+<link rel="import" href="../../../bower_components/iron-test-helpers/iron-test-helpers.html">
+<!--
+This must refer to the element this interface is wrapping around. Otherwise
+breaking changes to gr-change-actions won’t be noticed.
+-->
+<link rel="import" href="../../change/gr-change-actions/gr-change-actions.html">
+
+<test-fixture id="basic">
+  <template>
+    <gr-change-actions></gr-change-actions>
+  </template>
+</test-fixture>
+
+<script>
+  suite('gr-js-api-interface tests', function() {
+    var element;
+    var changeActions;
+
+    setup(function() {
+      element = fixture('basic');
+      var plugin;
+      Gerrit.install(function(p) { plugin = p; }, '0.1',
+          'http://test.com/plugins/testplugin/static/test.js');
+      changeActions = plugin.changeActions();
+    });
+
+    teardown(function() {
+      changeActions = null;
+    });
+
+    test('property existence', function() {
+      [
+        'ActionType',
+        'ChangeActions',
+        'RevisionActions',
+      ].forEach(function(p) {
+        assertArraysEqual(changeActions[p], element[p]);
+      });
+    });
+
+    // Because deepEqual doesn’t behave in Safari.
+    function assertArraysEqual(actual, expected) {
+      assert.equal(actual.length, expected.length);
+      for (var i = 0; i < actual.length; i++) {
+        assert.equal(actual[i], expected[i]);
+      }
+    }
+
+    test('add/remove primary action keys', function() {
+      element.primaryActionKeys = [];
+      changeActions.addPrimaryActionKey('foo');
+      assertArraysEqual(element.primaryActionKeys, ['foo']);
+      changeActions.addPrimaryActionKey('foo');
+      assertArraysEqual(element.primaryActionKeys, ['foo']);
+      changeActions.addPrimaryActionKey('bar');
+      assertArraysEqual(element.primaryActionKeys, ['foo', 'bar']);
+      changeActions.removePrimaryActionKey('foo');
+      assertArraysEqual(element.primaryActionKeys, ['bar']);
+      changeActions.removePrimaryActionKey('baz');
+      assertArraysEqual(element.primaryActionKeys, ['bar']);
+      changeActions.removePrimaryActionKey('bar');
+      assertArraysEqual(element.primaryActionKeys, []);
+    });
+
+    test('action buttons', function(done) {
+      var key = changeActions.add(changeActions.ActionType.REVISION, 'Bork!');
+      var handler = sinon.spy();
+      changeActions.addTapListener(key, handler);
+      flush(function() {
+        MockInteractions.tap(element.$$('[data-action-key="' + key + '"]'));
+        assert(handler.calledOnce);
+        changeActions.removeTapListener(key, handler);
+        MockInteractions.tap(element.$$('[data-action-key="' + key + '"]'));
+        assert(handler.calledOnce);
+        changeActions.remove(key);
+        flush(function() {
+          assert.isNull(element.$$('[data-action-key="' + key + '"]'));
+          done();
+        });
+      });
+    });
+
+    test('action button properties', function(done) {
+      var key = changeActions.add(changeActions.ActionType.REVISION, 'Bork!');
+      flush(function() {
+        var button = element.$$('[data-action-key="' + key + '"]');
+        assert.isOk(button);
+        assert.equal(button.getAttribute('data-label'), 'Bork!');
+        assert.isFalse(button.disabled);
+        changeActions.setLabel(key, 'Yo');
+        changeActions.setEnabled(key, false);
+        flush(function() {
+          assert.equal(button.getAttribute('data-label'), 'Yo');
+          assert.isTrue(button.disabled);
+          done();
+        });
+      });
+    });
+  });
+</script>
diff --git a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-change-reply-js-api.js b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-change-reply-js-api.js
new file mode 100644
index 0000000..9d6b83b
--- /dev/null
+++ b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-change-reply-js-api.js
@@ -0,0 +1,30 @@
+// Copyright (C) 2016 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+(function(window) {
+  'use strict';
+
+  function GrChangeReplyInterface(el) {
+    this._el = el;
+  }
+
+  GrChangeReplyInterface.prototype.setLabelValue = function(label, value) {
+    this._el.setLabelValue(label, value);
+  };
+
+  GrChangeReplyInterface.prototype.send = function() {
+    return this._el.send();
+  };
+
+  window.GrChangeReplyInterface = GrChangeReplyInterface;
+})(window);
diff --git a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-change-reply-js-api_test.html b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-change-reply-js-api_test.html
new file mode 100644
index 0000000..2e5aa56
--- /dev/null
+++ b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-change-reply-js-api_test.html
@@ -0,0 +1,70 @@
+<!DOCTYPE html>
+<!--
+Copyright (C) 2016 The Android Open Source Project
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+
+<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
+<title>gr-change-reply-js-api</title>
+
+<script src="../../../bower_components/webcomponentsjs/webcomponents.min.js"></script>
+<script src="../../../bower_components/web-component-tester/browser.js"></script>
+
+<link rel="import" href="../../../bower_components/iron-test-helpers/iron-test-helpers.html">
+<!--
+This must refer to the element this interface is wrapping around. Otherwise
+breaking changes to gr-reply-dialog won’t be noticed.
+-->
+<link rel="import" href="../../change/gr-reply-dialog/gr-reply-dialog.html">
+
+<test-fixture id="basic">
+  <template>
+    <gr-reply-dialog></gr-reply-dialog>
+  </template>
+</test-fixture>
+
+<script>
+  suite('gr-change-reply-js-api tests', function() {
+    var element;
+    var sandbox;
+    var changeReply;
+
+    setup(function() {
+      stub('gr-rest-api-interface', {
+        getAccount: function() { return Promise.resolve(null); },
+      });
+      element = fixture('basic');
+      sandbox = sinon.sandbox.create();
+      var plugin;
+      Gerrit.install(function(p) { plugin = p; }, '0.1',
+          'http://test.com/plugins/testplugin/static/test.js');
+      changeReply = plugin.changeReply();
+    });
+
+    teardown(function() {
+      changeReply = null;
+      sandbox.restore();
+    });
+
+    test('calls', function() {
+      var setLabelValueStub = sinon.stub(element, 'setLabelValue');
+      changeReply.setLabelValue('My-Label', '+1337');
+      assert(setLabelValueStub.calledWithExactly('My-Label', '+1337'));
+
+      var sendStub = sinon.stub(element, 'send');
+      changeReply.send();
+      assert(sendStub.calledWithExactly());
+    });
+  });
+</script>
diff --git a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-js-api-interface.html b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-js-api-interface.html
index a3b489d..1967b80 100644
--- a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-js-api-interface.html
+++ b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-js-api-interface.html
@@ -14,9 +14,12 @@
 limitations under the License.
 -->
 <link rel="import" href="../../../bower_components/polymer/polymer.html">
+<link rel="import" href="../gr-rest-api-interface/gr-rest-api-interface.html">
 
 <dom-module id="gr-js-api-interface">
   <template></template>
+  <script src="gr-change-actions-js-api.js"></script>
+  <script src="gr-change-reply-js-api.js"></script>
   <script src="gr-js-api-interface.js"></script>
   <script src="gr-public-js-api.js"></script>
 </dom-module>
diff --git a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-js-api-interface.js b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-js-api-interface.js
index cf4edf3..bb37085 100644
--- a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-js-api-interface.js
+++ b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-js-api-interface.js
@@ -16,6 +16,7 @@
 
   var EventType = {
     HISTORY: 'history',
+    LABEL_CHANGE: 'labelchange',
     SHOW_CHANGE: 'showchange',
     SUBMIT_CHANGE: 'submitchange',
     COMMENT: 'comment',
@@ -23,6 +24,7 @@
 
   var Element = {
     CHANGE_ACTIONS: 'changeactions',
+    REPLY_DIALOG: 'replydialog',
   };
 
   Polymer({
@@ -53,6 +55,9 @@
         case EventType.COMMENT:
           this._handleComment(detail);
           break;
+        case EventType.LABEL_CHANGE:
+          this._handleLabelChange(detail);
+          break;
         default:
           console.warn('handleEvent called with unsupported event type:', type);
           break;
@@ -133,6 +138,16 @@
       });
     },
 
+    _handleLabelChange: function(detail) {
+      this._getEventCallbacks(EventType.LABEL_CHANGE).forEach(function(cb) {
+        try {
+          cb(detail.change);
+        } catch (err) {
+          console.error(err);
+        }
+      });
+    },
+
     _getEventCallbacks: function(type) {
       return this._eventCallbacks[type] || [];
     },
diff --git a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-js-api-interface_test.html b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-js-api-interface_test.html
index 3be232e..5936bee 100644
--- a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-js-api-interface_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-js-api-interface_test.html
@@ -38,9 +38,14 @@
     };
 
     setup(function() {
+      stub('gr-rest-api-interface', {
+        getAccount: function() {
+          return Promise.resolve({name: 'Judy Hopps'});
+        },
+      })
       element = fixture('basic');
       errorStub = sinon.stub(console, 'error');
-      Gerrit.install(function(p) { plugin = p; },
+      Gerrit.install(function(p) { plugin = p; }, '0.1',
           'http://test.com/plugins/testplugin/static/test.js');
     });
 
@@ -97,6 +102,17 @@
       element.handleEvent(element.EventType.COMMENT, {node: testCommentNode});
     });
 
+    test('labelchange event', function(done) {
+      var testChange = {_number: 42};
+      plugin.on(element.EventType.LABEL_CHANGE, throwErrFn);
+      plugin.on(element.EventType.LABEL_CHANGE, function(change) {
+        assert.deepEqual(change, testChange);
+        assert.isTrue(errorStub.calledOnce);
+        done();
+      });
+      element.handleEvent(element.EventType.LABEL_CHANGE, {change: testChange});
+    });
+
     test('submitchange', function() {
       plugin.on(element.EventType.SUBMIT_CHANGE, throwErrFn);
       plugin.on(element.EventType.SUBMIT_CHANGE, function() { return true; });
@@ -108,5 +124,18 @@
       assert.isTrue(errorStub.calledTwice);
     });
 
+    test('versioning', function() {
+      var callback = sinon.spy();
+      Gerrit.install(callback, '0.0pre-alpha');
+      assert(callback.notCalled);
+    });
+
+    test('getAccount', function(done) {
+      Gerrit.getLoggedIn().then(function(loggedIn) {
+        assert.isTrue(loggedIn);
+        done();
+      });
+    });
+
   });
 </script>
diff --git a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-public-js-api.js b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-public-js-api.js
index 578f44d..21d76f1 100644
--- a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-public-js-api.js
+++ b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-public-js-api.js
@@ -14,11 +14,19 @@
 (function(window) {
   'use strict';
 
+  var API_VERSION = '0.1';
+
   // GWT JSNI uses $wnd to refer to window.
   // http://www.gwtproject.org/doc/latest/DevGuideCodingBasicsJSNI.html
   window.$wnd = window;
 
   function Plugin(opt_url) {
+    if (!opt_url) {
+      console.warn('Plugin not being loaded from /plugins base path.',
+          'Unable to determine name.');
+      return;
+    }
+
     this._url = new URL(opt_url);
     if (this._url.pathname.indexOf('/plugins') !== 0) {
       console.warn('Plugin not being loaded from /plugins base path:',
@@ -44,9 +52,14 @@
     return this._url.origin + '/plugins/' + this._name + (opt_path || '/');
   };
 
-  Plugin.prototype.getChangeActionsElement = function() {
-    return Plugin._sharedAPIElement.getElement(
-        Plugin._sharedAPIElement.Element.CHANGE_ACTIONS);
+  Plugin.prototype.changeActions = function() {
+    return new GrChangeActionsInterface(Plugin._sharedAPIElement.getElement(
+        Plugin._sharedAPIElement.Element.CHANGE_ACTIONS));
+  };
+
+  Plugin.prototype.changeReply = function() {
+    return new GrChangeReplyInterface(Plugin._sharedAPIElement.getElement(
+        Plugin._sharedAPIElement.Element.REPLY_DIALOG));
   };
 
   var Gerrit = window.Gerrit || {};
@@ -68,12 +81,22 @@
     return name;
   };
 
-  Gerrit.install = function(callback, opt_src) {
+  Gerrit.install = function(callback, opt_version, opt_src) {
+    if (opt_version && opt_version !== API_VERSION) {
+      console.warn('Only version ' + API_VERSION +
+          ' is supported in PolyGerrit. ' + opt_version + ' was given.');
+      return;
+    }
+
     // TODO(andybons): Polyfill currentScript for IE10/11 (edge supports it).
     var src = opt_src || (document.currentScript && document.currentScript.src);
     callback(new Plugin(src));
   };
 
+  Gerrit.getLoggedIn = function() {
+    return document.createElement('gr-rest-api-interface').getLoggedIn();
+  };
+
   Gerrit.installGwt = function() {
     // NOOP since PolyGerrit doesn’t support GWT plugins.
   };
diff --git a/polygerrit-ui/app/test/index.html b/polygerrit-ui/app/test/index.html
index 98f7eef..153db20 100644
--- a/polygerrit-ui/app/test/index.html
+++ b/polygerrit-ui/app/test/index.html
@@ -75,6 +75,8 @@
     'shared/gr-date-formatter/gr-date-formatter_test.html',
     'shared/gr-editable-content/gr-editable-content_test.html',
     'shared/gr-editable-label/gr-editable-label_test.html',
+    'shared/gr-js-api-interface/gr-change-actions-js-api_test.html',
+    'shared/gr-js-api-interface/gr-change-reply-js-api_test.html',
     'shared/gr-js-api-interface/gr-js-api-interface_test.html',
     'shared/gr-linked-text/gr-linked-text_test.html',
     'shared/gr-rest-api-interface/gr-rest-api-interface_test.html',