1// Copyright (c) 2011 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/** 7 * @fileoverview Implements an element that is hidden by default, but 8 * when shown, dims and (attempts to) disable the main document. 9 * 10 * You can turn any div into an overlay. Note that while an 11 * overlay element is shown, its parent is changed. Hiding the overlay 12 * restores its original parentage. 13 * 14 */ 15cr.define('gpu', function() { 16 /** 17 * Manages a full-window div that darkens the window, disables 18 * input, and hosts the currently-visible overlays. You shouldn't 19 * have to instantiate this directly --- it gets set automatically. 20 * @param {Object=} opt_propertyBag Optional properties. 21 * @constructor 22 * @extends {HTMLDivElement} 23 */ 24 var OverlayRoot = cr.ui.define('div'); 25 OverlayRoot.prototype = { 26 __proto__: HTMLDivElement.prototype, 27 decorate: function() { 28 this.classList.add('overlay-root'); 29 this.visible = false; 30 31 this.contentHost = this.ownerDocument.createElement('div'); 32 this.contentHost.classList.add('content-host'); 33 34 this.tabCatcher = this.ownerDocument.createElement('span'); 35 this.tabCatcher.tabIndex = 0; 36 37 this.appendChild(this.contentHost); 38 39 this.onKeydownBoundToThis_ = this.onKeydown_.bind(this); 40 this.onFocusInBoundToThis_ = this.onFocusIn_.bind(this); 41 this.addEventListener('mousedown', this.onMousedown_.bind(this)); 42 }, 43 44 /** 45 * Adds an overlay, attaching it to the contentHost so that it is visible. 46 */ 47 showOverlay: function(overlay) { 48 // Reparent this to the overlay content host. 49 overlay.oldParent_ = overlay.parentNode; 50 this.contentHost.appendChild(overlay); 51 this.contentHost.appendChild(this.tabCatcher); 52 53 // Show the overlay root. 54 this.ownerDocument.body.classList.add('disabled-by-overlay'); 55 this.visible = true; 56 57 // Bring overlay into focus. 58 overlay.tabIndex = 0; 59 overlay.focus(); 60 61 // Listen to key and focus events to prevent focus from 62 // leaving the overlay. 63 this.ownerDocument.addEventListener('focusin', 64 this.onFocusInBoundToThis_, true); 65 overlay.addEventListener('keydown', this.onKeydownBoundToThis_); 66 }, 67 68 /** 69 * Clicking outside of the overlay will de-focus the overlay. The 70 * next tab will look at the entire document to determine the focus. 71 * For certain documents, this can cause focus to "leak" outside of 72 * the overlay. 73 */ 74 onMousedown_: function(e) { 75 if (e.target == this) { 76 e.preventDefault(); 77 } 78 }, 79 80 /** 81 * Prevents forward-tabbing out of the overlay 82 */ 83 onFocusIn_: function(e) { 84 if (e.target == this.tabCatcher) { 85 window.setTimeout(this.focusOverlay_.bind(this), 0); 86 } 87 }, 88 89 focusOverlay_: function() { 90 this.contentHost.firstChild.focus(); 91 }, 92 93 /** 94 * Prevent the user from shift-tabbing backwards out of the overlay. 95 */ 96 onKeydown_: function(e) { 97 if (e.keyCode == 9 && 98 e.shiftKey && 99 e.target == this.contentHost.firstChild) { 100 e.preventDefault(); 101 } 102 }, 103 104 /** 105 * Hides an overlay, attaching it to its original parent if needed. 106 */ 107 hideOverlay: function(overlay) { 108 // hide the overlay root 109 this.visible = false; 110 this.ownerDocument.body.classList.remove('disabled-by-overlay'); 111 this.lastFocusOut_ = undefined; 112 113 // put the overlay back on its previous parent 114 overlay.parentNode.removeChild(this.tabCatcher); 115 if (overlay.oldParent_) { 116 overlay.oldParent_.appendChild(overlay); 117 delete overlay.oldParent_; 118 } else { 119 this.contentHost.removeChild(overlay); 120 } 121 122 // remove listeners 123 overlay.removeEventListener('keydown', this.onKeydownBoundToThis_); 124 this.ownerDocument.removeEventListener('focusin', 125 this.onFocusInBoundToThis_); 126 } 127 }; 128 129 cr.defineProperty(OverlayRoot, 'visible', cr.PropertyKind.BOOL_ATTR); 130 131 /** 132 * Creates a new overlay element. It will not be visible until shown. 133 * @param {Object=} opt_propertyBag Optional properties. 134 * @constructor 135 * @extends {HTMLDivElement} 136 */ 137 var Overlay = cr.ui.define('div'); 138 139 Overlay.prototype = { 140 __proto__: HTMLDivElement.prototype, 141 142 /** 143 * Initializes the overlay element. 144 */ 145 decorate: function() { 146 // create the overlay root on this document if its not present 147 if (!this.ownerDocument.querySelector('.overlay-root')) { 148 var overlayRoot = this.ownerDocument.createElement('div'); 149 cr.ui.decorate(overlayRoot, OverlayRoot); 150 this.ownerDocument.body.appendChild(overlayRoot); 151 } 152 153 this.classList.add('overlay'); 154 this.visible = false; 155 }, 156 157 onVisibleChanged_: function() { 158 var overlayRoot = this.ownerDocument.querySelector('.overlay-root'); 159 if (this.visible) { 160 overlayRoot.showOverlay(this); 161 } else { 162 overlayRoot.hideOverlay(this); 163 } 164 } 165 }; 166 167 /** 168 * Shows and hides the overlay. Note that while visible == true, the overlay 169 * element will be tempoarily reparented to another place in the DOM. 170 */ 171 cr.defineProperty(Overlay, 'visible', cr.PropertyKind.BOOL_ATTR, 172 Overlay.prototype.onVisibleChanged_); 173 174 return { 175 Overlay: Overlay 176 }; 177}); 178