1<!DOCTYPE html>
2<!--
3Copyright (c) 2014 The Chromium Authors. All rights reserved.
4Use of this source code is governed by a BSD-style license that can be
5found in the LICENSE file.
6-->
7
8<link rel="import" href="/tracing/base/utils.html">
9<link rel="import" href="/tracing/base/event.html">
10<link rel="import" href="/tracing/ui/base/ui.html">
11<link rel="import" href="/tracing/ui/base/utils.html">
12
13<template id="overlay-template">
14  <style>
15    overlay-mask {
16      left: 0;
17      padding: 8px;
18      position: absolute;
19      top: 0;
20      z-index: 1000;
21      font-family: sans-serif;
22      -webkit-justify-content: center;
23      background: rgba(0, 0, 0, 0.8);
24      display: -webkit-flex;
25      height: 100%;
26      left: 0;
27      position: fixed;
28      top: 0;
29      width: 100%;
30    }
31    overlay-mask:focus {
32      outline: none;
33    }
34    overlay-vertical-centering-container {
35      -webkit-justify-content: center;
36      -webkit-flex-direction: column;
37      display: -webkit-flex;
38    }
39    overlay-frame {
40      z-index: 1100;
41      background: rgb(255, 255, 255);
42      border: 1px solid #ccc;
43      margin: 75px;
44      display: -webkit-flex;
45      -webkit-flex-direction: column;
46      min-height: 0;
47    }
48    title-bar {
49      -webkit-align-items: center;
50      -webkit-flex-direction: row;
51      border-bottom: 1px solid #ccc;
52      background-color: #ddd;
53      display: -webkit-flex;
54      padding: 5px;
55      -webkit-flex: 0 0 auto;
56    }
57    title {
58      display: inline;
59      font-weight: bold;
60      -webkit-box-flex: 1;
61      -webkit-flex: 1 1 auto;
62    }
63    close-button {
64      -webkit-align-self: flex-end;
65      border: 1px solid #eee;
66      background-color: #999;
67      font-size: 10pt;
68      font-weight: bold;
69      padding: 2px;
70      text-align: center;
71      width: 16px;
72    }
73    close-button:hover {
74      background-color: #ddd;
75      border-color: black;
76      cursor: pointer;
77    }
78    overlay-content {
79      display: -webkit-flex;
80      -webkit-flex: 1 1 auto;
81      -webkit-flex-direction: column;
82      overflow-y: auto;
83      padding: 10px;
84      min-width: 300px;
85      min-height: 0;
86    }
87    button-bar {
88      -webkit-align-items: baseline;
89      border-top: 1px solid #ccc;
90      display: -webkit-flex;
91      -webkit-flex: 0 0 auto;
92      -webkit-flex-direction: row-reverse;
93      padding: 4px;
94    }
95  </style>
96
97  <overlay-mask>
98    <overlay-vertical-centering-container>
99      <overlay-frame>
100        <title-bar>
101          <title></title>
102          <close-button>&#x2715</close-button>
103        </title-bar>
104        <overlay-content>
105          <content></content>
106        </overlay-content>
107        <button-bar></button-bar>
108      </overlay-frame>
109    </overlay-vertical-centering-container>
110  </overlay-mask>
111</template>
112
113<script>
114'use strict';
115
116/**
117 * @fileoverview Implements an element that is hidden by default, but
118 * when shown, dims and (attempts to) disable the main document.
119 *
120 * You can turn any div into an overlay. Note that while an
121 * overlay element is shown, its parent is changed. Hiding the overlay
122 * restores its original parentage.
123 *
124 */
125tr.exportTo('tr.ui.b', function() {
126  if (tr.isHeadless)
127    return {};
128
129  var THIS_DOC = document.currentScript.ownerDocument;
130
131  /**
132   * Creates a new overlay element. It will not be visible until shown.
133   * @constructor
134   * @extends {HTMLDivElement}
135   */
136  var Overlay = tr.ui.b.define('overlay');
137
138  Overlay.prototype = {
139    __proto__: HTMLDivElement.prototype,
140
141    /**
142     * Initializes the overlay element.
143     */
144    decorate: function() {
145      this.classList.add('overlay');
146
147      this.parentEl_ = this.ownerDocument.body;
148
149      this.visible_ = false;
150      this.userCanClose_ = true;
151
152      this.onKeyDown_ = this.onKeyDown_.bind(this);
153      this.onClick_ = this.onClick_.bind(this);
154      this.onFocusIn_ = this.onFocusIn_.bind(this);
155      this.onDocumentClick_ = this.onDocumentClick_.bind(this);
156      this.onClose_ = this.onClose_.bind(this);
157
158      this.addEventListener('visible-change',
159          tr.ui.b.Overlay.prototype.onVisibleChange_.bind(this), true);
160
161      // Setup the shadow root
162      var createShadowRoot = this.createShadowRoot ||
163          this.webkitCreateShadowRoot;
164      this.shadow_ = createShadowRoot.call(this);
165      this.shadow_.appendChild(tr.ui.b.instantiateTemplate('#overlay-template',
166                                                        THIS_DOC));
167
168      this.closeBtn_ = this.shadow_.querySelector('close-button');
169      this.closeBtn_.addEventListener('click', this.onClose_);
170
171      this.shadow_
172          .querySelector('overlay-frame')
173          .addEventListener('click', this.onClick_);
174
175      this.observer_ = new WebKitMutationObserver(
176          this.didButtonBarMutate_.bind(this));
177      this.observer_.observe(this.shadow_.querySelector('button-bar'),
178                             { childList: true });
179
180      // title is a variable on regular HTMLElements. However, we want to
181      // use it for something more useful.
182      Object.defineProperty(
183          this, 'title', {
184            get: function() {
185              return this.shadow_.querySelector('title').textContent;
186            },
187            set: function(title) {
188              this.shadow_.querySelector('title').textContent = title;
189            }
190          });
191    },
192
193    set userCanClose(userCanClose) {
194      this.userCanClose_ = userCanClose;
195      this.closeBtn_.style.display =
196          userCanClose ? 'block' : 'none';
197    },
198
199    get buttons() {
200      return this.shadow_.querySelector('button-bar');
201    },
202
203    get visible() {
204      return this.visible_;
205    },
206
207    set visible(newValue) {
208      if (this.visible_ === newValue)
209        return;
210
211      this.visible_ = newValue;
212      var e = new tr.b.Event('visible-change');
213      this.dispatchEvent(e);
214    },
215
216    onVisibleChange_: function() {
217      this.visible_ ? this.show_() : this.hide_();
218    },
219
220    show_: function() {
221      this.parentEl_.appendChild(this);
222
223      if (this.userCanClose_) {
224        this.addEventListener('keydown', this.onKeyDown_.bind(this));
225        this.addEventListener('click', this.onDocumentClick_.bind(this));
226      }
227
228      this.parentEl_.addEventListener('focusin', this.onFocusIn_);
229      this.tabIndex = 0;
230
231      // Focus the first thing we find that makes sense. (Skip the close button
232      // as it doesn't make sense as the first thing to focus.)
233      var focusEl = undefined;
234      var elList = this.querySelectorAll('button, input, list, select, a');
235      if (elList.length > 0) {
236        if (elList[0] === this.closeBtn_) {
237          if (elList.length > 1)
238            focusEl = elList[1];
239        } else {
240          focusEl = elList[0];
241        }
242      }
243      if (focusEl === undefined)
244        focusEl = this;
245      focusEl.focus();
246    },
247
248    hide_: function() {
249      this.parentEl_.removeChild(this);
250
251      this.parentEl_.removeEventListener('focusin', this.onFocusIn_);
252
253      if (this.closeBtn_)
254        this.closeBtn_.removeEventListener('click', this.onClose_);
255
256      document.removeEventListener('keydown', this.onKeyDown_);
257      document.removeEventListener('click', this.onDocumentClick_);
258    },
259
260    onClose_: function(e) {
261      this.visible = false;
262      if ((e.type != 'keydown') ||
263          (e.type === 'keydown' && e.keyCode === 27))
264        e.stopPropagation();
265      e.preventDefault();
266      tr.b.dispatchSimpleEvent(this, 'closeclick');
267    },
268
269    onFocusIn_: function(e) {
270      if (e.target === this)
271        return;
272
273      window.setTimeout(function() { this.focus(); }, 0);
274      e.preventDefault();
275      e.stopPropagation();
276    },
277
278    didButtonBarMutate_: function(e) {
279      var hasButtons = this.buttons.children.length > 0;
280      if (hasButtons)
281        this.shadow_.querySelector('button-bar').style.display = undefined;
282      else
283        this.shadow_.querySelector('button-bar').style.display = 'none';
284    },
285
286    onKeyDown_: function(e) {
287      // Disallow shift-tab back to another element.
288      if (e.keyCode === 9 &&  // tab
289          e.shiftKey &&
290          e.target === this) {
291        e.preventDefault();
292        return;
293      }
294
295      if (e.keyCode !== 27)  // escape
296        return;
297
298      this.onClose_(e);
299    },
300
301    onClick_: function(e) {
302      e.stopPropagation();
303    },
304
305    onDocumentClick_: function(e) {
306      if (!this.userCanClose_)
307        return;
308
309      this.onClose_(e);
310    }
311  };
312
313  Overlay.showError = function(msg, opt_err) {
314    var o = new Overlay();
315    o.title = 'Error';
316    o.textContent = msg;
317    if (opt_err) {
318      var e = tr.b.normalizeException(opt_err);
319
320      var stackDiv = document.createElement('pre');
321      stackDiv.textContent = e.stack;
322      stackDiv.style.paddingLeft = '8px';
323      stackDiv.style.margin = 0;
324      o.appendChild(stackDiv);
325    }
326    var b = document.createElement('button');
327    b.textContent = 'OK';
328    b.addEventListener('click', function() {
329      o.visible = false;
330    });
331    o.buttons.appendChild(b);
332    o.visible = true;
333    return o;
334  }
335
336  return {
337    Overlay: Overlay
338  };
339});
340</script>
341