Plugin popup() api

Changes to JS API:
- popups have backdrop and is centered
- plugin.popup() takes a string custom element name
- plugin.popup() returns an API for closing and reopening the popup
- plugin.deprecated.popup() takes Element (similar to GWT Plugin JS API)

Recommended usage:

``` js
  Gerrit.install(function(plugin) {
    const popup = plugin.popup('my-plugin-popup-simple');;
    // ... work
    popup.close();
    // ... more work
    popup.open();
  });

```

``` html
<dom-module id="my-plugin-popup-simple">
  <template>
    <div>popup popup popup popup popup </div>
  </template>
  <script>
    Polymer({is: 'my-plugin-popup-simple'});
  </script>
</dom-module>
```

Change-Id: Icb3f83d35f3c60915f12b77bc8a7d548d50d5695
diff --git a/polygerrit-ui/app/elements/gr-app.html b/polygerrit-ui/app/elements/gr-app.html
index defbe8a..406a4a7 100644
--- a/polygerrit-ui/app/elements/gr-app.html
+++ b/polygerrit-ui/app/elements/gr-app.html
@@ -46,6 +46,7 @@
 <link rel="import" href="./core/gr-reporting/gr-reporting.html">
 <link rel="import" href="./core/gr-router/gr-router.html">
 <link rel="import" href="./diff/gr-diff-view/gr-diff-view.html">
+<link rel="import" href="./plugins/gr-endpoint-decorator/gr-endpoint-decorator.html">
 <link rel="import" href="./plugins/gr-external-style/gr-external-style.html">
 <link rel="import" href="./plugins/gr-plugin-host/gr-plugin-host.html">
 <link rel="import" href="./settings/gr-cla-view/gr-cla-view.html">
@@ -201,6 +202,7 @@
           on-close="_handleRegistrationDialogClose">
       </gr-registration-dialog>
     </gr-overlay>
+    <gr-endpoint-decorator name="plugin-overlay"></gr-endpoint-decorator>
     <gr-error-manager id="errorManager"></gr-error-manager>
     <gr-rest-api-interface id="restAPI"></gr-rest-api-interface>
     <gr-reporting id="reporting"></gr-reporting>
diff --git a/polygerrit-ui/app/elements/plugins/gr-popup-interface/gr-plugin-popup.html b/polygerrit-ui/app/elements/plugins/gr-popup-interface/gr-plugin-popup.html
new file mode 100644
index 0000000..3ccb3fd
--- /dev/null
+++ b/polygerrit-ui/app/elements/plugins/gr-popup-interface/gr-plugin-popup.html
@@ -0,0 +1,28 @@
+<!--
+Copyright (C) 2017 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.
+-->
+
+<link rel="import" href="../../../bower_components/polymer/polymer.html">
+<link rel="import" href="../../shared/gr-overlay/gr-overlay.html">
+
+<dom-module id="gr-plugin-popup">
+  <template>
+    <style include="shared-styles"></style>
+    <gr-overlay id="overlay" with-backdrop>
+      <content></content>
+    </gr-overlay>
+  </template>
+  <script src="gr-plugin-popup.js"></script>
+</dom-module>
diff --git a/polygerrit-ui/app/elements/plugins/gr-popup-interface/gr-plugin-popup.js b/polygerrit-ui/app/elements/plugins/gr-popup-interface/gr-plugin-popup.js
new file mode 100644
index 0000000..8286eae
--- /dev/null
+++ b/polygerrit-ui/app/elements/plugins/gr-popup-interface/gr-plugin-popup.js
@@ -0,0 +1,28 @@
+// Copyright (C) 2017 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';
+  Polymer({
+    is: 'gr-plugin-popup',
+    get opened() {
+      return this.$.overlay.opened;
+    },
+    open() {
+      return this.$.overlay.open();
+    },
+    close() {
+      this.$.overlay.close();
+    },
+  });
+})(window);
diff --git a/polygerrit-ui/app/elements/plugins/gr-popup-interface/gr-plugin-popup_test.html b/polygerrit-ui/app/elements/plugins/gr-popup-interface/gr-plugin-popup_test.html
new file mode 100644
index 0000000..2dbf96d
--- /dev/null
+++ b/polygerrit-ui/app/elements/plugins/gr-popup-interface/gr-plugin-popup_test.html
@@ -0,0 +1,67 @@
+<!DOCTYPE html>
+<!--
+Copyright (C) 2017 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-plugin-popup</title>
+
+<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
+<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<link rel="import" href="../../../test/common-test-setup.html"/>
+<link rel="import" href="gr-plugin-popup.html"/>
+
+<script>void(0);</script>
+
+<test-fixture id="basic">
+  <template>
+    <gr-plugin-popup></gr-plugin-popup>
+  </template>
+</test-fixture>
+
+<script>
+  suite('gr-plugin-popup tests', () => {
+    let element;
+    let sandbox;
+
+    setup(() => {
+      sandbox = sinon.sandbox.create();
+      element = fixture('basic');
+      stub('gr-overlay', {
+        open: sandbox.stub().returns(Promise.resolve()),
+        close: sandbox.stub(),
+      });
+    });
+
+    teardown(() => {
+      sandbox.restore();
+    });
+
+    test('exists', () => {
+      assert.isOk(element);
+    });
+
+    test('open uses open() from gr-overlay', () => {
+      return element.open().then(() => {
+        assert.isTrue(element.$.overlay.open.called);
+      });
+    });
+
+    test('close uses close() from gr-overlay', () => {
+      element.close();
+      assert.isTrue(element.$.overlay.close.called);
+    });
+  });
+</script>
diff --git a/polygerrit-ui/app/elements/plugins/gr-popup-interface/gr-popup-interface.html b/polygerrit-ui/app/elements/plugins/gr-popup-interface/gr-popup-interface.html
new file mode 100644
index 0000000..6bf37de
--- /dev/null
+++ b/polygerrit-ui/app/elements/plugins/gr-popup-interface/gr-popup-interface.html
@@ -0,0 +1,23 @@
+<!--
+Copyright (C) 2017 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.
+-->
+
+<link rel="import" href="../../../bower_components/polymer/polymer.html">
+<link rel="import" href="../../shared/gr-js-api-interface/gr-js-api-interface.html">
+<link rel="import" href="gr-plugin-popup.html">
+
+<dom-module id="gr-popup-interface">
+  <script src="gr-popup-interface.js"></script>
+</dom-module>
diff --git a/polygerrit-ui/app/elements/plugins/gr-popup-interface/gr-popup-interface.js b/polygerrit-ui/app/elements/plugins/gr-popup-interface/gr-popup-interface.js
new file mode 100644
index 0000000..e62e882
--- /dev/null
+++ b/polygerrit-ui/app/elements/plugins/gr-popup-interface/gr-popup-interface.js
@@ -0,0 +1,71 @@
+// Copyright (C) 2017 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';
+
+  /**
+   * Plugin popup API.
+   * Provides method for opening and closing popups from plugin.
+   * opt_moduleName is a name of custom element that will be automatically
+   * inserted on popup opening.
+   * @param {!Object} plugin
+   * @param {opt_moduleName=} string
+   */
+  function GrPopupInterface(plugin, opt_moduleName) {
+    this.plugin = plugin;
+    this._openingPromise = null;
+    this._popup = null;
+    this._moduleName = opt_moduleName || null;
+  }
+
+  GrPopupInterface.prototype._getElement = function() {
+    return Polymer.dom(this._popup);
+  };
+
+  /**
+   * Opens the popup, inserts it into DOM over current UI.
+   * Creates the popup if not previously created. Creates popup content element,
+   * if it was provided with constructor.
+   * @returns {!Promise<!Object>}
+   */
+  GrPopupInterface.prototype.open = function() {
+    if (!this._openingPromise) {
+      this._openingPromise =
+          this.plugin.hook('plugin-overlay').getLastAttached()
+      .then(hookEl => {
+        const popup = document.createElement('gr-plugin-popup');
+        if (this._moduleName) {
+          const el = Polymer.dom(popup).appendChild(
+              document.createElement(this._moduleName));
+          el.plugin = this.plugin;
+        }
+        this._popup = Polymer.dom(hookEl).appendChild(popup);
+        Polymer.dom.flush();
+        return this._popup.open().then(() => this);
+      });
+    }
+    return this._openingPromise;
+  };
+
+  /**
+   * Hides the popup.
+   */
+  GrPopupInterface.prototype.close = function() {
+    if (!this._popup) { return; }
+    this._popup.close();
+    this._openingPromise = null;
+  };
+
+  window.GrPopupInterface = GrPopupInterface;
+})(window);
diff --git a/polygerrit-ui/app/elements/plugins/gr-popup-interface/gr-popup-interface_test.html b/polygerrit-ui/app/elements/plugins/gr-popup-interface/gr-popup-interface_test.html
new file mode 100644
index 0000000..7d9dd28
--- /dev/null
+++ b/polygerrit-ui/app/elements/plugins/gr-popup-interface/gr-popup-interface_test.html
@@ -0,0 +1,112 @@
+<!DOCTYPE html>
+<!--
+Copyright (C) 2017 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-popup-interface</title>
+
+<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
+<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<link rel="import" href="../../../test/common-test-setup.html"/>
+<link rel="import" href="gr-popup-interface.html"/>
+
+<script>void(0);</script>
+
+<test-fixture id="container">
+  <template>
+    <div></div>
+  </template>
+</test-fixture>
+
+<dom-module id="gr-user-test-popup">
+  <template>
+    <div id="barfoo">some test module</div>
+  </template>
+  <script>Polymer({is: 'gr-user-test-popup'});</script>
+</dom-module>
+
+<script>
+  suite('gr-popup-interface tests', () => {
+    let container;
+    let instance;
+    let plugin;
+    let sandbox;
+
+    setup(() => {
+      sandbox = sinon.sandbox.create();
+      Gerrit.install(p => { plugin = p; }, '0.1',
+          'http://test.com/plugins/testplugin/static/test.js');
+      container = fixture('container');
+      sandbox.stub(plugin, 'hook').returns({
+        getLastAttached() {
+          return Promise.resolve(container);
+        },
+      });
+    });
+
+    teardown(() => {
+      sandbox.restore();
+    });
+
+    suite('manual', () => {
+      setup(() => {
+        instance = new GrPopupInterface(plugin);
+      });
+
+      test('open', () => {
+        return instance.open().then(api => {
+          assert.strictEqual(api, instance);
+          const manual = document.createElement('div');
+          manual.id = 'foobar';
+          manual.innerHTML = 'manual content';
+          api._getElement().appendChild(manual);
+          flushAsynchronousOperations();
+          assert.equal(
+              container.querySelector('#foobar').textContent, 'manual content');
+        });
+      });
+
+      test('close', () => {
+        return instance.open().then(api => {
+          assert.isTrue(api._getElement().node.opened);
+          api.close();
+          assert.isFalse(api._getElement().node.opened);
+        });
+      });
+    });
+
+    suite('components', () => {
+      setup(() => {
+        instance = new GrPopupInterface(plugin, 'gr-user-test-popup');
+      });
+
+      test('open', () => {
+        return instance.open().then(api => {
+          assert.isNotNull(
+              Polymer.dom(container).querySelector('gr-user-test-popup'));
+        });
+      });
+
+      test('close', () => {
+        return instance.open().then(api => {
+          assert.isTrue(api._getElement().node.opened);
+          api.close();
+          assert.isFalse(api._getElement().node.opened);
+        });
+      });
+    });
+  });
+</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 f6e2b64..53f889f 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
@@ -19,6 +19,7 @@
 <link rel="import" href="../../core/gr-reporting/gr-reporting.html">
 <link rel="import" href="../../plugins/gr-attribute-helper/gr-attribute-helper.html">
 <link rel="import" href="../../plugins/gr-dom-hooks/gr-dom-hooks.html">
+<link rel="import" href="../../plugins/gr-popup-interface/gr-popup-interface.html">
 <link rel="import" href="../../plugins/gr-theme-api/gr-theme-api.html">
 <link rel="import" href="../../shared/gr-rest-api-interface/gr-rest-api-interface.html">
 <link rel="import" href="../gr-rest-api-interface/gr-rest-api-interface.html">
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 ca0f372..7c9033e 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
@@ -338,5 +338,35 @@
             'http://test.com/r/plugins/testplugin/static/test.js');
       });
     });
+
+    suite('popup', () => {
+      test('popup(element) is deprecated', () => {
+        assert.throws(() => {
+          plugin.popup(document.createElement('div'));
+        });
+      });
+
+      test('popup(moduleName) creates popup with component', () => {
+        const openStub = sandbox.stub();
+        sandbox.stub(window, 'GrPopupInterface').returns({
+          open: openStub,
+        });
+        plugin.popup('some-name');
+        assert.isTrue(openStub.calledOnce);
+        assert.isTrue(GrPopupInterface.calledWith(plugin, 'some-name'));
+      });
+
+      test('deprecated.popup(element) creates popup with element', () => {
+        const el = document.createElement('div');
+        el.textContent = 'some text here';
+        const openStub = sandbox.stub(GrPopupInterface.prototype, 'open');
+        openStub.returns(Promise.resolve({
+          _getElement() {
+            return document.createElement('div');
+          }}));
+        plugin.deprecated.popup(el);
+        assert.isTrue(openStub.calledOnce);
+      });
+    });
   });
 </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 6c3db84..f1d607c 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
@@ -76,6 +76,10 @@
       return;
     }
     this._name = pathname.split('/')[2];
+
+    this.deprecated = {
+      popup: deprecatedAPI.popup.bind(this),
+    };
   }
 
   Plugin._sharedAPIElement = document.createElement('gr-js-api-interface');
@@ -176,6 +180,24 @@
     return new GrAttributeHelper(element);
   };
 
+  Plugin.prototype.popup = function(moduleName) {
+    if (typeof moduleName !== 'string') {
+      throw new Error('deprecated, use deprecated.popup');
+    }
+    const api = new GrPopupInterface(this, moduleName);
+    return api.open();
+  };
+
+  const deprecatedAPI = {};
+  deprecatedAPI.popup = function(el) {
+    console.warn('plugin.deprecated.popup() is deprecated!');
+    if (!el) {
+      throw new Error('Popup contents not found');
+    }
+    const api = new GrPopupInterface(this);
+    api.open().then(api => api._getElement().appendChild(el));
+  };
+
   const Gerrit = window.Gerrit || {};
 
   // Number of plugins to initialize, -1 means 'not yet known'.