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>✕</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