1010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis// Copyright (c) 2012 The Chromium Authors. All rights reserved.
2010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis// Use of this source code is governed by a BSD-style license that can be
3010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis// found in the LICENSE file.
4010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis
5010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis
6010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis/**
7010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * @fileoverview Implements an element that is hidden by default, but
8010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * when shown, dims and (attempts to) disable the main document.
9010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis *
10010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * You can turn any div into an overlay. Note that while an
11010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * overlay element is shown, its parent is changed. Hiding the overlay
12010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis * restores its original parentage.
13010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis *
14010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis */
15010583560e0e6db74fe50b840bce46ba6537de63Jamie Genniscr.define('tracing', function() {
16010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis  /**
17010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis   * Manages a full-window div that darkens the window, disables
18010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis   * input, and hosts the currently-visible overlays. You shouldn't
19010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis   * have to instantiate this directly --- it gets set automatically.
20010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis   * @param {Object=} opt_propertyBag Optional properties.
21010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis   * @constructor
22010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis   * @extends {HTMLDivElement}
23010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis   */
24010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis  var OverlayRoot = cr.ui.define('div');
25010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis  OverlayRoot.prototype = {
26010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis    __proto__: HTMLDivElement.prototype,
27010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis    decorate: function() {
28010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis      this.classList.add('overlay-root');
29010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis      this.visible = false;
30010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis
31010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis      this.contentHost = this.ownerDocument.createElement('div');
32010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis      this.contentHost.classList.add('content-host');
33010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis
34010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis      this.tabCatcher = this.ownerDocument.createElement('span');
35010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis      this.tabCatcher.tabIndex = 0;
36010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis
37010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis      this.appendChild(this.contentHost);
38010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis
39010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis      this.onKeydownBoundToThis_ = this.onKeydown_.bind(this);
40010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis      this.onFocusInBoundToThis_ = this.onFocusIn_.bind(this);
41010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis      this.addEventListener('mousedown', this.onMousedown_.bind(this));
42010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis    },
43010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis
44010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis    /**
45010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis     * Adds an overlay, attaching it to the contentHost so that it is visible.
46010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis     */
47010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis    showOverlay: function(overlay) {
48010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis      // Reparent this to the overlay content host.
49010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis      overlay.oldParent_ = overlay.parentNode;
50010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis      this.contentHost.appendChild(overlay);
51010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis      this.contentHost.appendChild(this.tabCatcher);
52010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis
53010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis      // Show the overlay root.
54010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis      this.ownerDocument.body.classList.add('disabled-by-overlay');
55010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis      this.visible = true;
56010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis
57010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis      // Bring overlay into focus.
58010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis      overlay.tabIndex = 0;
59010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis      var focusElement =
60010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis          overlay.querySelector('button, input, list, select, a');
61010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis      if (!focusElement) {
62010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis        focusElement = overlay;
63010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis      }
64010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis      focusElement.focus();
65010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis
66010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis      // Listen to key and focus events to prevent focus from
67010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis      // leaving the overlay.
68010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis      this.ownerDocument.addEventListener('focusin',
69010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis          this.onFocusInBoundToThis_, true);
70010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis      overlay.addEventListener('keydown', this.onKeydownBoundToThis_);
71010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis    },
72010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis
73010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis    /**
74010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis     * Clicking outside of the overlay will de-focus the overlay. The
75010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis     * next tab will look at the entire document to determine the focus.
76010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis     * For certain documents, this can cause focus to "leak" outside of
77010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis     * the overlay.
78010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis     */
79010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis    onMousedown_: function(e) {
80010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis      if (e.target == this) {
81010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis        e.preventDefault();
82010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis      }
83010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis    },
84010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis
85010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis    /**
86010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis     * Prevents forward-tabbing out of the overlay
87010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis     */
88010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis    onFocusIn_: function(e) {
89010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis      if (e.target == this.tabCatcher) {
90010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis        window.setTimeout(this.focusOverlay_.bind(this), 0);
91010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis      }
92010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis    },
93010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis
94010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis    focusOverlay_: function() {
95010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis      this.contentHost.firstChild.focus();
96010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis    },
97010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis
98010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis    /**
99010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis     * Prevent the user from shift-tabbing backwards out of the overlay.
100010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis     */
101010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis    onKeydown_: function(e) {
102010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis      if (e.keyCode == 9 &&
103010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis          e.shiftKey &&
104010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis          e.target == this.contentHost.firstChild) {
105010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis        e.preventDefault();
106010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis      }
107010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis    },
108010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis
109010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis    /**
110010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis     * Hides an overlay, attaching it to its original parent if needed.
111010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis     */
112010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis    hideOverlay: function(overlay) {
113010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis      // hide the overlay root
114010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis      this.visible = false;
115010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis      this.ownerDocument.body.classList.remove('disabled-by-overlay');
116010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis      this.lastFocusOut_ = undefined;
117010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis
118010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis      // put the overlay back on its previous parent
119010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis      overlay.parentNode.removeChild(this.tabCatcher);
120010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis      if (overlay.oldParent_) {
121010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis        overlay.oldParent_.appendChild(overlay);
122010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis        delete overlay.oldParent_;
123010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis      } else {
124010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis        this.contentHost.removeChild(overlay);
125010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis      }
126010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis
127010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis      // remove listeners
128010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis      overlay.removeEventListener('keydown', this.onKeydownBoundToThis_);
129010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis      this.ownerDocument.removeEventListener('focusin',
130010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis          this.onFocusInBoundToThis_);
131010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis    }
132010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis  };
133010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis
134010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis  cr.defineProperty(OverlayRoot, 'visible', cr.PropertyKind.BOOL_ATTR);
135010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis
136010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis  /**
137010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis   * Creates a new overlay element. It will not be visible until shown.
138010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis   * @param {Object=} opt_propertyBag Optional properties.
139010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis   * @constructor
140010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis   * @extends {HTMLDivElement}
141010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis   */
142010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis  var Overlay = cr.ui.define('div');
143010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis
144010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis  Overlay.prototype = {
145010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis    __proto__: HTMLDivElement.prototype,
146010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis
147010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis    /**
148010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis     * Initializes the overlay element.
149010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis     */
150010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis    decorate: function() {
151010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis      // create the overlay root on this document if its not present
152010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis      if (!this.ownerDocument.querySelector('.overlay-root')) {
153010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis        var overlayRoot = this.ownerDocument.createElement('div');
154010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis        cr.ui.decorate(overlayRoot, OverlayRoot);
155010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis        this.ownerDocument.body.appendChild(overlayRoot);
156010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis      }
157010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis
158010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis      this.classList.add('overlay');
159010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis      this.visible = false;
160010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis    },
161010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis
162010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis    onVisibleChanged_: function() {
163010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis      var overlayRoot = this.ownerDocument.querySelector('.overlay-root');
164010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis      if (this.visible) {
165010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis        overlayRoot.showOverlay(this);
166010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis      } else {
167010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis        overlayRoot.hideOverlay(this);
168010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis      }
169010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis    }
170010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis  };
171010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis
172010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis  /**
173010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis   * Shows and hides the overlay. Note that while visible == true, the overlay
174010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis   * element will be tempoarily reparented to another place in the DOM.
175010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis   */
176010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis  cr.defineProperty(Overlay, 'visible', cr.PropertyKind.BOOL_ATTR,
177010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis      Overlay.prototype.onVisibleChanged_);
178010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis
179010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis  return {
180010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis    Overlay: Overlay
181010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis  };
182010583560e0e6db74fe50b840bce46ba6537de63Jamie Gennis});
183