1// Copyright (c) 2012 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5/** 6 * @fileoverview Provides dialog-like behaviors for the tracing UI. 7 */ 8cr.define('cr.ui.overlay', function() { 9 /** 10 * Gets the top, visible overlay. It makes the assumption that if multiple 11 * overlays are visible, the last in the byte order is topmost. 12 * TODO(estade): rely on aria-visibility instead? 13 * @return {HTMLElement} The overlay. 14 */ 15 function getTopOverlay() { 16 var overlays = document.querySelectorAll('.overlay:not([hidden])'); 17 return overlays[overlays.length - 1]; 18 } 19 20 /** 21 * Returns a visible default button of the overlay, if it has one. If the 22 * overlay has more than one, the first one will be returned. 23 * 24 * @param {HTMLElement} overlay The .overlay. 25 * @return {HTMLElement} The default button. 26 */ 27 function getDefaultButton(overlay) { 28 function isHidden(node) { return node.hidden; } 29 var defaultButtons = 30 overlay.querySelectorAll('.page .button-strip > .default-button'); 31 for (var i = 0; i < defaultButtons.length; i++) { 32 if (!findAncestor(defaultButtons[i], isHidden)) 33 return defaultButtons[i]; 34 } 35 return null; 36 } 37 38 /** @type {boolean} */ 39 var globallyInitialized = false; 40 41 /** 42 * Makes initializations which must hook at the document level. 43 */ 44 function globalInitialization() { 45 if (!globallyInitialized) { 46 document.addEventListener('keydown', function(e) { 47 var overlay = getTopOverlay(); 48 if (!overlay) 49 return; 50 51 // Close the overlay on escape. 52 if (e.keyCode == 27) // Escape 53 cr.dispatchSimpleEvent(overlay, 'cancelOverlay'); 54 55 // Execute the overlay's default button on enter, unless focus is on an 56 // element that has standard behavior for the enter key. 57 var forbiddenTagNames = /^(A|BUTTON|SELECT|TEXTAREA)$/; 58 if (e.keyIdentifier == 'Enter' && 59 !forbiddenTagNames.test(document.activeElement.tagName)) { 60 var button = getDefaultButton(overlay); 61 if (button) { 62 button.click(); 63 // Executing the default button may result in focus moving to a 64 // different button. Calling preventDefault is necessary to not have 65 // that button execute as well. 66 e.preventDefault(); 67 } 68 } 69 }); 70 71 window.addEventListener('resize', setMaxHeightAllPages); 72 globallyInitialized = true; 73 } 74 75 setMaxHeightAllPages(); 76 } 77 78 /** 79 * Sets the max-height of all pages in all overlays, based on the window 80 * height. 81 */ 82 function setMaxHeightAllPages() { 83 var pages = document.querySelectorAll( 84 '.overlay .page:not(.not-resizable)'); 85 86 var maxHeight = Math.min(0.9 * window.innerHeight, 640) + 'px'; 87 for (var i = 0; i < pages.length; i++) 88 pages[i].style.maxHeight = maxHeight; 89 } 90 91 /** 92 * Adds behavioral hooks for the given overlay. 93 * @param {HTMLElement} overlay The .overlay. 94 */ 95 function setupOverlay(overlay) { 96 // Close the overlay on clicking any of the pages' close buttons. 97 var closeButtons = overlay.querySelectorAll('.page > .close-button'); 98 for (var i = 0; i < closeButtons.length; i++) { 99 closeButtons[i].addEventListener('click', function(e) { 100 cr.dispatchSimpleEvent(overlay, 'cancelOverlay'); 101 }); 102 } 103 104 // Remove the 'pulse' animation any time the overlay is hidden or shown. 105 overlay.__defineSetter__('hidden', function(value) { 106 this.classList.remove('pulse'); 107 if (value) 108 this.setAttribute('hidden', true); 109 else 110 this.removeAttribute('hidden'); 111 }); 112 overlay.__defineGetter__('hidden', function() { 113 return this.hasAttribute('hidden'); 114 }); 115 116 // Shake when the user clicks away. 117 overlay.addEventListener('click', function(e) { 118 // Only pulse if the overlay was the target of the click. 119 if (this != e.target) 120 return; 121 122 // This may be null while the overlay is closing. 123 var overlayPage = this.querySelector('.page:not([hidden])'); 124 if (overlayPage) 125 overlayPage.classList.add('pulse'); 126 }); 127 overlay.addEventListener('webkitAnimationEnd', function(e) { 128 e.target.classList.remove('pulse'); 129 }); 130 } 131 132 return { 133 globalInitialization: globalInitialization, 134 setupOverlay: setupOverlay, 135 }; 136}); 137