Home | History | Annotate | Download | only in base
      1 <!DOCTYPE html>
      2 <!--
      3 Copyright (c) 2014 The Chromium Authors. All rights reserved.
      4 Use of this source code is governed by a BSD-style license that can be
      5 found 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  */
    125 tr.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