Home | History | Annotate | Download | only in gpu_internals
      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  */
     15 cr.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