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 */ 8 cr.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('.overlay .page'); 84 85 var maxHeight = Math.min(0.9 * window.innerHeight, 640) + 'px'; 86 for (var i = 0; i < pages.length; i++) 87 pages[i].style.maxHeight = maxHeight; 88 } 89 90 /** 91 * Adds behavioral hooks for the given overlay. 92 * @param {HTMLElement} overlay The .overlay. 93 */ 94 function setupOverlay(overlay) { 95 // Close the overlay on clicking any of the pages' close buttons. 96 var closeButtons = overlay.querySelectorAll('.page > .close-button'); 97 for (var i = 0; i < closeButtons.length; i++) { 98 closeButtons[i].addEventListener('click', function(e) { 99 cr.dispatchSimpleEvent(overlay, 'cancelOverlay'); 100 }); 101 } 102 103 // Remove the 'pulse' animation any time the overlay is hidden or shown. 104 overlay.__defineSetter__('hidden', function(value) { 105 this.classList.remove('pulse'); 106 if (value) 107 this.setAttribute('hidden', true); 108 else 109 this.removeAttribute('hidden'); 110 }); 111 overlay.__defineGetter__('hidden', function() { 112 return this.hasAttribute('hidden'); 113 }); 114 115 // Shake when the user clicks away. 116 overlay.addEventListener('click', function(e) { 117 // Only pulse if the overlay was the target of the click. 118 if (this != e.target) 119 return; 120 121 // This may be null while the overlay is closing. 122 var overlayPage = this.querySelector('.page:not([hidden])'); 123 if (overlayPage) 124 overlayPage.classList.add('pulse'); 125 }); 126 overlay.addEventListener('webkitAnimationEnd', function(e) { 127 e.target.classList.remove('pulse'); 128 }); 129 } 130 131 return { 132 globalInitialization: globalInitialization, 133 setupOverlay: setupOverlay, 134 }; 135 }); 136