1cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)// Copyright 2014 The Chromium Authors. All rights reserved.
2cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)// Use of this source code is governed by a BSD-style license that can be
3cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)// found in the LICENSE file.
4cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
5cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)/**
6cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @fileoverview Draws and animates the graphical indicator around the active
7cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) *    object or text range, and handles animation when the indicator is moving.
8cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) */
9cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
10cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
11cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)goog.provide('cvox.ActiveIndicator');
12cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
13cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)goog.require('cvox.Cursor');
14cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)goog.require('cvox.DomUtil');
15cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
16cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
17cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)/**
18cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * Constructs and ActiveIndicator, a glowing outline around whatever
19cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * node or text range is currently active. Initially it won't display
20cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * anything; call syncToNode, syncToRange, or syncToCursorSelection to
21cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * make it animate and move. It only displays when this window/iframe
22cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * has focus.
23cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) *
24cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @constructor
25cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) */
26cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)cvox.ActiveIndicator = function() {
27cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  /**
28cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)   * The time when the indicator was most recently moved.
29cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)   * @type {number}
30cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)   * @private
31cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)   */
32cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  this.lastMoveTime_ = 0;
33cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
34cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  /**
35cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)   * An estimate of the current zoom factor of the webpage. This is
36cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)   * needed in order to accurately line up the different pieces of the
37cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)   * indicator border and avoid rounding errors.
38cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)   * @type {number}
39cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)   * @private
40cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)   */
41cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  this.zoom_ = 1;
42cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
43cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  /**
44cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)   * The parent element of the indicator.
45cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)   * @type {?Element}
46cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)   * @private
47cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)   */
48cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  this.container_ = null;
49cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
50cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  /**
51cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)   * The current indicator rects.
52cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)   * @type {Array.<ClientRect>}
53cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)   * @private
54cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)   */
55cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  this.rects_ = null;
56cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
57cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  /**
58cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)   * The most recent target of a call to syncToNode, syncToRange, or
59cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)   * syncToCursorSelection.
60cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)   * @type {Array.<Node>|Range}
61cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)   * @private
62cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)   */
63cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  this.lastSyncTarget_ = null;
64cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
65cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  /**
66cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)   * The most recent client rects for the active indicator, so we
67cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)   * can tell when it moved.
68cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)   * @type {ClientRectList|Array.<ClientRect>}
69cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)   * @private
70cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)   */
71cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  this.lastClientRects_ = null;
72cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
73cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  /**
74cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)   * The id from window.setTimeout when updating the indicator if needed.
75cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)   * @type {?number}
76cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)   * @private
77cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)   */
78cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  this.updateIndicatorTimeoutId_ = null;
79cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
80cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  /**
81cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)   * True if this window is blurred and we shouldn't show the indicator.
82cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)   * @type {boolean}
83cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)   * @private
84cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)   */
85cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  this.blurred_ = false;
86cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
87cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  /**
88cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)   * A cached value of window height.
89cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)   * @type {number|undefined}
90cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)   * @private
91cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)   */
92cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  this.innerHeight_;
93cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
94cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  /**
95cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)   * A cached value of window width.
96cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)   * @type {number|undefined}
97cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)   * @private
98cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)   */
99cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  this.innerWidth_;
100cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
101cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  // Hide the indicator when the window doesn't have focus.
102cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  window.addEventListener('focus', goog.bind(function() {
103cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    this.blurred_ = false;
104cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    if (this.container_) {
105cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      this.container_.classList.remove('cvox_indicator_window_not_focused');
106cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    }
107cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  }, this), false);
108cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  window.addEventListener('blur', goog.bind(function() {
109cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    this.blurred_ = true;
110cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    if (this.container_) {
111cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      this.container_.classList.add('cvox_indicator_window_not_focused');
112cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    }
113cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  }, this), false);
114cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)};
115cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
116cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)/**
117cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * CSS for the active indicator. The basic hierarchy looks like this:
118cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) *
119cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * container (pulsing) (animate_normal, animate_quick)
120cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) *   region (visible)
121cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) *     top
122cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) *     middle_nw
123cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) *     middle_ne
124cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) *     middle_sw
125cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) *     middle_se
126cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) *     bottom
127cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) *   region (visible)
128cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) *     top
129cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) *     middle_nw
130cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) *     middle_ne
131cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) *     middle_sw
132cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) *     middle_se
133cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) *     bottom
134cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) *
135cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @type {string}
136cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @const
137cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) */
138cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)cvox.ActiveIndicator.STYLE =
139cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    '.cvox_indicator_container {' +
140cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    '  position: absolute !important;' +
141cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    '  left: 0 !important;' +
142cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    '  top: 0 !important;' +
143cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    '  z-index: 2147483647 !important;' +
144cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    '  pointer-events: none !important;' +
145cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    '  margin: 0px !important;' +
146cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    '  padding: 0px !important;' +
147cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    '}' +
148cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    '.cvox_indicator_window_not_focused {' +
149cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    '  visibility: hidden !important;' +
150cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    '}' +
151cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    '.cvox_indicator_pulsing {' +
152cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    '  -webkit-animation: ' +
153cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    // NOTE(deboer): This animation is 0 seconds long to work around
154cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    // http://crbug.com/128993.  Revert it to 2s when the bug is fixed.
155cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    '      cvox_indicator_pulsing_animation 0s 2 alternate !important;' +
156cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    '  -webkit-animation-timing-function: ease-in-out !important;' +
157cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    '}' +
158cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    '.cvox_indicator_region {' +
159cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    '  opacity: 0 !important;' +
160cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    '  -webkit-transition: opacity 1s !important;' +
161cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    '}' +
162cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    '.cvox_indicator_visible {' +
163cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    '  opacity: 1 !important;' +
164cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    '}' +
165cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    '.cvox_indicator_container .cvox_indicator_region * {' +
166cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    '  position:absolute !important;' +
167cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    '  box-shadow: 0 0 4px 4px #f7983a !important;' +
168cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    '  border-radius: 6px !important;' +
169cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    '  margin: 0px !important;' +
170cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    '  padding: 0px !important;' +
171cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    '  -webkit-transition: none !important;' +
172cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    '}' +
173cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    '.cvox_indicator_animate_normal .cvox_indicator_region * {' +
174cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    '  -webkit-transition: all 0.3s !important;' +
175cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    '}' +
176cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    '.cvox_indicator_animate_quick .cvox_indicator_region * {' +
177cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    '  -webkit-transition: all 0.1s !important;' +
178cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    '}' +
179cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    '.cvox_indicator_top {' +
180cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    '  border-radius: inherit inherit 0 0 !important;' +
181cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    '}' +
182cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    '.cvox_indicator_middle_nw {' +
183cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    '  border-radius: inherit 0 0 0 !important;' +
184cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    '}' +
185cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    '.cvox_indicator_middle_ne {' +
186cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    '  border-radius: 0 inherit 0 0 !important;' +
187cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    '}' +
188cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    '.cvox_indicator_middle_se {' +
189cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    '  border-radius: 0 0 inherit 0 !important;' +
190cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    '}' +
191cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    '.cvox_indicator_middle_sw {' +
192cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    '  border-radius: 0 0 0 inherit !important;' +
193cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    '}' +
194cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    '.cvox_indicator_bottom {' +
195cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    '  border-radius: 0 0 inherit inherit !important;' +
196cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    '}' +
197cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    '@-webkit-keyframes cvox_indicator_pulsing_animation {' +
198cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    '   0% {opacity: 1.0}' +
199cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    '  50% {opacity: 0.5}' +
200cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    ' 100% {opacity: 1.0}' +
201cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    '}';
202cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
203cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)/**
204cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * The minimum number of milliseconds that must have elapsed
205cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * since the last navigation for a quick animation to be allowed.
206cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @type {number}
207cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @const
208cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) */
209cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)cvox.ActiveIndicator.QUICK_ANIM_DELAY_MS = 100;
210cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
211cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)/**
212cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * The minimum number of milliseconds that must have elapsed
213cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * since the last navigation for a normal (slower) animation
214cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * to be allowed.
215cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @type {number}
216cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @const
217cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) */
218cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)cvox.ActiveIndicator.NORMAL_ANIM_DELAY_MS = 300;
219cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
220cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)/**
221cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * Margin between the active object's rect and the indicator border.
222cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @type {number}
223cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @const
224cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) */
225cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)cvox.ActiveIndicator.MARGIN = 8;
226cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
227cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)/**
228cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * Remove the indicator from the DOM.
229cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) */
230cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)cvox.ActiveIndicator.prototype.removeFromDom = function() {
231cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  if (this.container_ && this.container_.parentElement) {
232cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    this.container_.parentElement.removeChild(this.container_);
233cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  }
234cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)};
235cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
236cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)/**
237cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * Move the indicator to surround the given node.
238cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @param {Node} node The new target of the indicator.
239cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) */
240cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)cvox.ActiveIndicator.prototype.syncToNode = function(node) {
241cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  if (!node) {
242cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    return;
243cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  }
244cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  // In the navigation manager, and specifically the node walkers, focusing
245cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  // on the body means we are before the beginning of the document.  In
246cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  // that case, we simply hide the active indicator.
247cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  if (node == document.body) {
248cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    this.removeFromDom();
249cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    return;
250cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  }
251cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  this.syncToNodes([node]);
252cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)};
253cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
254cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)/**
255cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * Move the indicator to surround the given nodes.
256cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @param {Array.<Node>} nodes The new targets of the indicator.
257cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) */
258cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)cvox.ActiveIndicator.prototype.syncToNodes = function(nodes) {
259cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  var clientRects = this.clientRectsFromNodes_(nodes);
260cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  this.moveIndicator_(clientRects, cvox.ActiveIndicator.MARGIN);
261cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  this.lastSyncTarget_ = nodes;
262cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  this.lastClientRects_ = clientRects;
263cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  if (this.updateIndicatorTimeoutId_ != null) {
264cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    window.clearTimeout(this.updateIndicatorTimeoutId_);
265cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    this.updateIndicatorTimeoutId_ = null;
266cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  }
267cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)};
268cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
269cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)/**
270cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * Move the indicator to surround the given range.
271cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @param {Range} range The range.
272cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) */
273cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)cvox.ActiveIndicator.prototype.syncToRange = function(range) {
274cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  var margin = cvox.ActiveIndicator.MARGIN;
275cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  if (range.startContainer == range.endContainer &&
276cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      range.startOffset + 1 == range.endOffset) {
277cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    margin = 1;
278cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  }
279cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
280cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  var clientRects = range.getClientRects();
281cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  this.moveIndicator_(clientRects, margin);
282cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  this.lastSyncTarget_ = range;
283cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  this.lastClientRects_ = clientRects;
284cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  if (this.updateIndicatorTimeoutId_ != null) {
285cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    window.clearTimeout(this.updateIndicatorTimeoutId_);
286cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    this.updateIndicatorTimeoutId_ = null;
287cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  }
288cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)};
289cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
290cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)/**
291cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * Move the indicator to surround the given cursor range.
292cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @param {!cvox.CursorSelection} sel The start cursor position.
293cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) */
294cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)cvox.ActiveIndicator.prototype.syncToCursorSelection = function(sel) {
295cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  if (sel.start.node == sel.end.node && sel.start.index == sel.end.index) {
296cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    this.syncToNode(sel.start.node);
297cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  } else {
298cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    var range = document.createRange();
299cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    range.setStart(sel.start.node, sel.start.index);
300cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    range.setEnd(sel.end.node, sel.end.index);
301cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    this.syncToRange(range);
302cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  }
303cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)};
304cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
305cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)/**
306cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * Called when we should check to see if the indicator target has moved.
307cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * Schedule it after a short delay so that we don't waste a lot of time
308cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * updating.
309cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) */
310cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)cvox.ActiveIndicator.prototype.updateIndicatorIfChanged = function() {
311cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  if (this.updateIndicatorTimeoutId_) {
312cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    return;
313cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  }
314cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  this.updateIndicatorTimeoutId_ = window.setTimeout(goog.bind(function() {
315cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    this.handleUpdateIndicatorIfChanged_();
316cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  }, this), 100);
317cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)};
318cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
319cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)/**
320cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * Called when we should check to see if the indicator target has moved.
321cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * Schedule it after a short delay so that we don't waste a lot of time
322cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * updating.
323cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @private
324cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) */
325cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)cvox.ActiveIndicator.prototype.handleUpdateIndicatorIfChanged_ = function() {
326cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  this.updateIndicatorTimeoutId_ = null;
327cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  if (!this.lastSyncTarget_) {
328cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    return;
329cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  }
330cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
331cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  var newClientRects;
332cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  if (this.lastSyncTarget_ instanceof Array) {
333cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    newClientRects = this.clientRectsFromNodes_(this.lastSyncTarget_);
334cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  } else {
335cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    newClientRects = this.lastSyncTarget_.getClientRects();
336cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  }
337cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  if (!newClientRects || newClientRects.length == 0) {
338cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    this.syncToNode(document.body);
339cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    return;
340cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  }
341cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
342cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  var needsUpdate = false;
343cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  if (newClientRects.length != this.lastClientRects_.length) {
344cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    needsUpdate = true;
345cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  } else {
346cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    for (var i = 0; i < this.lastClientRects_.length; ++i) {
347cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      var last = this.lastClientRects_[i];
348cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      var current = newClientRects[i];
349cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      if (last.top != current.top ||
350cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)          last.right != current.right ||
351cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)          last.bottom != current.bottom ||
352cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)          last.left != last.left) {
353cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        needsUpdate = true;
354cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        break;
355cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      }
356cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    }
357cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  }
358cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  if (needsUpdate) {
359cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    this.moveIndicator_(newClientRects, cvox.ActiveIndicator.MARGIN);
360cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    this.lastClientRects_ = newClientRects;
361cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  }
362cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)};
363cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
364cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)/**
365cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @param {Array.<Node>} nodes An array of nodes.
366cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @return {Array.<ClientRect>} An array of client rects corresponding to
367cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) *     those nodes.
368cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @private
369cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) */
370cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)cvox.ActiveIndicator.prototype.clientRectsFromNodes_ = function(nodes) {
371cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  var clientRects = [];
372cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  for (var i = 0; i < nodes.length; ++i) {
373cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    var node = nodes[i];
374cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    if (node.constructor == Text) {
375cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      var range = document.createRange();
376cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      range.selectNode(node);
377cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      var rangeRects = range.getClientRects();
378cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      for (var j = 0; j < rangeRects.length; ++j)
379cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        clientRects.push(rangeRects[j]);
380cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    } else {
381cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      while (!node.getClientRects) {
382cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        node = node.parentElement;
383cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      }
384cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      var nodeRects = node.getClientRects();
385cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      for (var j = 0; j < nodeRects.length; ++j)
386cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        clientRects.push(nodeRects[j]);
387cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    }
388cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  }
389cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  return clientRects;
390cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)};
391cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
392cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)/**
393cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * Move the indicator from its current location, if any, to surround
394cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * the given set of rectanges.
395cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) *
396cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * The rectangles need not be contiguous - they're automatically
397cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * grouped into contiguous regions. The first region is "primary" - it
398cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * gets animated smoothly from the previous location to the new location.
399cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * Any other region (like, for example, a text range
400cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * that continues on a second column) gets a temporary outline that
401cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * disappears as soon as the indicator moves again.
402cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) *
403cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * A single region does not have to be rectangular - a region outline
404cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * is designed to handle the slightly non-rectangular shape of a typical
405cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * text paragraph, but not anything more complicated than that.
406cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) *
407cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @param {ClientRectList|Array.<ClientRect>} immutableRects The object rectangles.
408cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @param {number} margin Margin in pixels.
409cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @private
410cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) */
411cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)cvox.ActiveIndicator.prototype.moveIndicator_ = function(
412cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    immutableRects, margin) {
413cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  // Never put the active indicator into the DOM when the whole page is
414cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  // contentEditable; it will end up part of content that the user may
415cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  // be trying to edit.
416cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  if (document.body.isContentEditable) {
417cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    this.removeFromDom();
418cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    return;
419cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  }
420cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
421cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  var n = immutableRects.length;
422cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  if (n == 0) {
423cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    return;
424cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  }
425cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
426cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  // Offset the rects by documentElement, body, and/or scroll offsets,
427cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  // while copying them into a new mutable array.
428cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  var offsetX;
429cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  var offsetY;
430cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  if (window.getComputedStyle(document.body, null).position != 'static') {
431cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    offsetX = -document.body.getBoundingClientRect().left;
432cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    offsetY = -document.body.getBoundingClientRect().top;
4335f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)  } else if (window.getComputedStyle(document.documentElement, null).position !=
4345f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)                 'static') {
435cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    offsetX = -document.documentElement.getBoundingClientRect().left;
436cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    offsetY = -document.documentElement.getBoundingClientRect().top;
437cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  } else {
438cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    offsetX = window.pageXOffset;
439cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    offsetY = window.pageYOffset;
440cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  }
441cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
442cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  var rects = [];
443cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  for (var i = 0; i < n; i++) {
444cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    rects.push(
445cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        this.inset_(immutableRects[i], offsetX, offsetY, -offsetX, -offsetY));
446cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  }
447cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
448cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  // Create and attach the container if it doesn't exist or if it was detached.
449cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  if (!this.container_ || !this.container_.parentElement) {
450cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    // In case there are any detached containers around, clean them up. One case
451cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    // that requires clean up like this is when users download a file on Chrome
452cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    // on Android.
453cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    var oldContainers =
454cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        document.getElementsByClassName('cvox_indicator_container');
455cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    for (var j = 0, oldContainer; oldContainer = oldContainers[j]; j++) {
456cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      if (oldContainer.parentNode) {
457cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        oldContainer.parentNode.removeChild(oldContainer);
458cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      }
459cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    }
460cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    this.container_ = this.createDiv_(
461cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        document.body, 'cvox_indicator_container', document.body.firstChild);
462cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  }
463cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
464cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  // Add the CSS style to the page if it's not already there.
465cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  var style = document.createElement('style');
466cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  style.id = 'cvox_indicator_style';
467cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  style.innerHTML = cvox.ActiveIndicator.STYLE;
468cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  cvox.DomUtil.addNodeToHead(style, style.id);
469cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
470cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  // Decide on the animation speed. By default we do a medium-speed
471cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  // animation between the previous and new location. If the user is
472cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  // moving rapidly, we do a fast animation, or no animation.
473cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  var now = new Date().getTime();
474cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  var delta = now - this.lastMoveTime_;
475cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  this.container_.className = 'cvox_indicator_container';
476cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  if (!document.hasFocus() || this.blurred_) {
477cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    this.container_.classList.add('cvox_indicator_window_not_focused');
478cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  }
479cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  if (delta > cvox.ActiveIndicator.NORMAL_ANIM_DELAY_MS) {
480cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    this.container_.classList.add('cvox_indicator_animate_normal');
481cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  } else if (delta > cvox.ActiveIndicator.QUICK_ANIM_DELAY_MS) {
482cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    this.container_.classList.add('cvox_indicator_animate_quick');
483cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  }
484cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  this.lastMoveTime_ = now;
485cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
486cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  // Compute the zoom level of the browser - this is needed to avoid
487cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  // roundoff errors when placing the various pieces of the region
488cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  // outline.
489cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  this.computeZoomLevel_();
490cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
491cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  // Make it start pulsing after it's drawn the first frame - this is so
492cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  // that the opacity is always 100% when the indicator appears, and only
493cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  // starts pulsing afterwards.
494cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  window.setTimeout(goog.bind(function() {
495cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    this.container_.classList.add('cvox_indicator_pulsing');
496cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  }, this), 0);
497cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
498cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  // If there was more than one region previously, delete all except
499cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  // the first one.
500cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  while (this.container_.childElementCount > 1) {
501cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    this.container_.removeChild(this.container_.lastElementChild);
502cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  }
503cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
504cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  // Split the rects into contiguous regions.
505cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  var regions = [[rects[0]]];
506cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  var regionRects = [rects[0]];
507cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  for (i = 1; i < rects.length; i++) {
508cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    var found = false;
509cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    for (var j = 0; j < regions.length && !found; j++) {
510cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      if (this.intersects_(rects[i], regionRects[j])) {
511cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        regions[j].push(rects[i]);
512cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        regionRects[j] = this.union_(regionRects[j], rects[i]);
513cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        found = true;
514cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      }
515cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    }
516cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    if (!found) {
517cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      regions.push([rects[i]]);
518cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      regionRects.push(rects[i]);
519cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    }
520cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  }
521cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
522cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  // Keep merging regions that intersect.
523cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  // TODO(dmazzoni): reduce the worst-case complexity! This appears like
524cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  // it could be O(n^3), make sure it's not in practice.
525cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  do {
526cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    var merged = false;
527cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    for (i = 0; i < regions.length - 1 && !merged; i++) {
528cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      for (j = i + 1; j < regions.length && !merged; j++) {
529cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        if (this.intersects_(regionRects[i], regionRects[j])) {
530cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)          regions[i] = regions[i].concat(regions[j]);
531cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)          regionRects[i] = this.union_(regionRects[i], regionRects[j]);
532cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)          regions.splice(j, 1);
533cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)          regionRects.splice(j, 1);
534cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)          merged = true;
535cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        }
536cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      }
537cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    }
538cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  } while (merged);
539cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
540cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  // Sort rects within each region by y and then x position.
541cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  for (i = 0; i < regions.length; i++) {
542cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    regions[i].sort(function(r1, r2) {
543cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      if (r1.top != r2.top) {
544cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        return r1.top - r2.top;
545cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      } else {
546cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        return r1.left - r2.left;
547cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      }
548cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    });
549cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  }
550cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
551cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  // Draw each indicator region. The first region attempts to re-use the
552cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  // existing elements (which results in animating the transition).
553cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  for (i = 0; i < regions.length; i++) {
554cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    var parent = null;
555cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    if (i == 0 &&
556cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        this.container_.childElementCount == 1 &&
557cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        this.container_.children[0].childElementCount == 6) {
558cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      parent = this.container_.children[0];
559cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    }
560cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    this.updateIndicatorRegion_(regions[i], parent, margin);
561cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  }
562cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)};
563cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
564cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)/**
565cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * Update one indicator region - a set of contiguous rectangles on the
566cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * page.
567cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) *
568cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * A region is made up of six pieces, designed to handle the shape of a
569cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * typical text paragraph:
570cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) *
571cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) *              TOP TOP TOP
572cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) *              TOP     TOP
573cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) *  NW NW NW NW NW      NE NE NE NE NE NE NE NE NE
574cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) *  NW                                          NE
575cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) *  NW                                          NE
576cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) *  SW                                          SE
577cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) *  SW                                          SE
578cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) *  SW SW BOTTOM                      BOTTOM SE SE
579cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) *        BOTTOM                      BOTTOM
580cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) *        BOTTOM BOTTOM BOTTOM BOTTOM BOTTOM
581cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) *
582cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * When there's only a single rectangle - like when outlining something
583cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * simple like a button, all six pieces are still used - this makes the
584cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * animation smooth when sliding from a paragraph to a rectangular object
585cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * and then to another paragraph, for example:
586cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) *
587cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) *       TOP TOP TOP TOP TOP TOP TOP
588cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) *       TOP                     TOP
589cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) *       NW                       NE
590cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) *       NW                       NE
591cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) *       SW                       SE
592cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) *       SW                       SE
593cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) *       BOTTOM               BOTTOM
594cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) *       BOTTOM BOTTOM BOTTOM BOTTOM
595cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) *
596cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * Each piece is just a div that uses CSS to absolutely position itself.
597cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * The outline effect is done using the 'box-shadow' property around the
598cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * whole box, with the 'clip' property used to make sure that only 2 - 3
599cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * sides of the box are actually shown.
600cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) *
601cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * This code is very subtle! If you want to adjust something by a few
602cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * pixels, be prepared to do LOTS of testing!
603cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) *
604cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * Tip: while debugging, comment out the clipping and make each rectangle
605cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * a different color. That will make it much easier to see where each piece
606cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * starts and ends.
607cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) *
608cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @param {Array.<ClientRect>} rects The list of rects in the region.
609cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) *     These should already be sorted (top to bottom and left to right).
610cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @param {?Element} parent If present, try to reuse the existing element
611cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) *     (and animate the transition).
612cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @param {number} margin Margin in pixels.
613cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @private
614cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) */
615cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)cvox.ActiveIndicator.prototype.updateIndicatorRegion_ = function(
616cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    rects, parent, margin) {
617cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  if (parent) {
618cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    // Reuse the existing element (so we animate to the new location).
619cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    var regionTop = parent.children[0];
620cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    var regionMiddleNW = parent.children[1];
621cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    var regionMiddleNE = parent.children[2];
622cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    var regionMiddleSW = parent.children[3];
623cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    var regionMiddleSE = parent.children[4];
624cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    var regionBottom = parent.children[5];
625cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  } else {
626cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    // Create a new region (when the indicator first appears, or when
627cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    // this is a secondary region, like for text continuing on a second
628cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    // column).
629cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    parent = this.createDiv_(this.container_, 'cvox_indicator_region');
630cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    window.setTimeout(function() {
631cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      parent.classList.add('cvox_indicator_visible');
632cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    }, 0);
633cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    regionTop = this.createDiv_(parent, 'cvox_indicator_top');
634cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    regionMiddleNW = this.createDiv_(parent, 'cvox_indicator_middle_nw');
635cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    regionMiddleNE = this.createDiv_(parent, 'cvox_indicator_middle_ne');
636cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    regionMiddleSW = this.createDiv_(parent, 'cvox_indicator_middle_sw');
637cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    regionMiddleSE = this.createDiv_(parent, 'cvox_indicator_middle_se');
638cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    regionBottom = this.createDiv_(parent, 'cvox_indicator_bottom');
639cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  }
640cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
641cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  // Grab all of the rectangles in the top row.
642cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  var topRect = rects[0];
643cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  var topMiddle = Math.floor((topRect.top + topRect.bottom) / 2);
644cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  var topIndex = 1;
645cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  var n = rects.length;
646cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  while (topIndex < n && rects[topIndex].top < topMiddle) {
647cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    topRect = this.union_(topRect, rects[topIndex]);
648cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    topMiddle = Math.floor((topRect.top + topRect.bottom) / 2);
649cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    topIndex++;
650cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  }
651cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
652cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  if (topIndex == n) {
653cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    // Everything fits on one line, so use special case code to form
654cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    // the region into a rectangle.
655cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    var r = this.inset_(topRect, -margin, -margin, -margin, -margin);
656cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    var q1 = Math.floor((3 * r.top + 1 * r.bottom) / 4);
657cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    var q2 = Math.floor((2 * r.top + 2 * r.bottom) / 4);
658cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    var q3 = Math.floor((1 * r.top + 3 * r.bottom) / 4);
659cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    this.setElementCoords_(regionTop, r.left, r.top, r.right, q1,
660cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)                                      true, true, true, false);
661cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    this.setElementCoords_(regionMiddleNW, r.left, q1, r.left, q2,
662cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)                                           true, true, false, false);
663cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    this.setElementCoords_(regionMiddleSW, r.left, q2, r.left, q3,
664cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)                                           true, false, false, true);
665cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    this.setElementCoords_(regionMiddleNE, r.right, q1, r.right, q2,
666cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)                                           false, true, true, false);
667cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    this.setElementCoords_(regionMiddleSE, r.right, q2, r.right, q3,
668cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)                                           false, false, true, true);
669cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    this.setElementCoords_(regionBottom, r.left, q3, r.right, r.bottom,
670cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)                                         true, false, true, true);
671cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    return;
672cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  }
673cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
674cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  // Start from the end and grab all of the rectangles in the bottom row.
675cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  var bottomRect = rects[n - 1];
676cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  var bottomMiddle = Math.floor((bottomRect.top + bottomRect.bottom) / 2);
677cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  var bottomIndex = n - 2;
678cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  while (bottomIndex >= 0 && rects[bottomIndex].bottom > bottomMiddle) {
679cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    bottomRect = this.union_(bottomRect, rects[bottomIndex]);
680cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    bottomMiddle = Math.floor((bottomRect.top + bottomRect.bottom) / 2);
681cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    bottomIndex--;
682cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  }
683cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
684cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  // Extend the top and bottom rectangles a bit.
685cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  topRect = this.inset_(topRect, -margin, -margin, -margin, margin);
686cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  bottomRect = this.inset_(bottomRect, -margin, margin, -margin, -margin);
687cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
688cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  // Whatever's in-between the top and bottom is the "middle".
689cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  var middleRect;
690cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  if (topIndex > bottomIndex) {
691cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    middleRect = this.union_(topRect, bottomRect);
692cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    middleRect.top = topRect.bottom;
693cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    middleRect.bottom = bottomRect.top;
694cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    middleRect.height = Math.floor((middleRect.top + middleRect.bottom) / 2);
695cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  } else {
696cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    middleRect = rects[topIndex];
697cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    var middleIndex = topIndex + 1;
698cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    while (middleIndex <= bottomIndex) {
699cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      middleRect = this.union_(middleRect, rects[middleIndex]);
700cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      middleIndex++;
701cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    }
702cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    middleRect = this.inset_(middleRect, -margin, -margin, -margin, -margin);
703cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    middleRect.left = Math.min(
704cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        middleRect.left, topRect.left, bottomRect.left);
705cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    middleRect.right = Math.max(
706cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        middleRect.right, topRect.right, bottomRect.right);
707cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    middleRect.width = middleRect.right - middleRect.left;
708cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  }
709cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
710cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  // If the top or bottom is pretty close to the edge of the middle box,
711cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  // make them flush.
712cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  if (topRect.right > middleRect.right - 40) {
713cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    topRect.right = middleRect.right;
714cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    topRect.width = topRect.right - topRect.left;
715cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  }
716cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  if (topRect.left < middleRect.left + 40) {
717cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    topRect.left = middleRect.left;
718cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    topRect.width = topRect.right - topRect.left;
719cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  }
720cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  if (bottomRect.right > middleRect.right - 40) {
721cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    bottomRect.right = middleRect.right;
722cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    bottomRect.width = bottomRect.right - bottomRect.left;
723cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  }
724cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  if (bottomRect.left < middleRect.left + 40) {
725cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    bottomRect.left = middleRect.left;
726cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    bottomRect.width = bottomRect.right - bottomRect.left;
727cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  }
728cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
729cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  var midline = Math.floor((middleRect.top + middleRect.bottom) / 2);
730cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
731cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  this.setElementRect_(regionTop, topRect, true, true, true, false);
732cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  this.setElementRect_(regionBottom, bottomRect, true, false, true, true);
733cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
734cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  this.setElementCoords_(
735cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      regionMiddleNW,
736cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      middleRect.left, topRect.bottom, topRect.left, midline,
737cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      true, true, false, false);
738cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  this.setElementCoords_(
739cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      regionMiddleNE,
740cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      topRect.right, topRect.bottom,
741cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      middleRect.right, midline,
742cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      false, true, true, false);
743cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  this.setElementCoords_(
744cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      regionMiddleSW,
745cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      middleRect.left, midline, bottomRect.left, bottomRect.top,
746cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      true, false, false, true);
747cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  this.setElementCoords_(
748cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      regionMiddleSE,
749cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      bottomRect.right, midline,
750cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      middleRect.right, bottomRect.top,
751cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      false, false, true, true);
752cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)};
753cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
754cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)/**
755cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * Given two rectangles, return whether or not they intersect
756cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * (including a bit of slop, so if they're almost touching, we
757cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * return true).
758cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @param {ClientRect} r1 The first rect.
759cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @param {ClientRect} r2 The second rect.
760cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @return {boolean} Whether or not they intersect.
761cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @private
762cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) */
763cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)cvox.ActiveIndicator.prototype.intersects_ = function(r1, r2) {
764cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  var slop = 2 * cvox.ActiveIndicator.MARGIN;
765cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  return (r2.left <= r1.right + slop &&
766cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)          r2.right >= r1.left - slop &&
767cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)          r2.top <= r1.bottom + slop &&
768cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)          r2.bottom >= r1.top - slop);
769cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)};
770cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
771cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)/**
772cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * Given two rectangles, compute their union.
773cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @param {ClientRect} r1 The first rect.
774cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @param {ClientRect} r2 The second rect.
775cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @return {ClientRect} The union of the two rectangles.
776cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @private
777cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @suppress {invalidCasts} invalid cast - must be a subtype or supertype
778cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * from: {bottom: number, height: number, left: number, right: number, ...}
779cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * to  : (ClientRect|null)
780cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) */
781cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)cvox.ActiveIndicator.prototype.union_ = function(r1, r2) {
782cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  var result = {
783cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    left: Math.min(r1.left, r2.left),
784cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    top: Math.min(r1.top, r2.top),
785cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    right: Math.max(r1.right, r2.right),
786cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    bottom: Math.max(r1.bottom, r2.bottom)
787cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  };
788cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  result.width = result.right - result.left;
789cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  result.height = result.bottom - result.top;
790cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  return /** @type {ClientRect} */(result);
791cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)};
792cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
793cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)/**
794cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * Given a rectangle and four offsets, return a new rectangle inset by
795cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * the given offsets.
796cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @param {ClientRect} r The first rect.
797cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @param {number} left The left inset.
798cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @param {number} top The top inset.
799cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @param {number} right The right inset.
800cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @param {number} bottom The bottom inset.
801cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @return {ClientRect} The new rectangle.
802cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @private
803cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @suppress {invalidCasts} invalid cast - must be a subtype or supertype
804cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * from: {bottom: number, height: number, left: number, right: number, ...}
805cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * to  : (ClientRect|null)
806cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) */
807cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)cvox.ActiveIndicator.prototype.inset_ = function(r, left, top, right, bottom) {
808cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  var result = {
809cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    left: r.left + left,
810cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    top: r.top + top,
811cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    right: r.right - right,
812cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    bottom: r.bottom - bottom
813cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  };
814cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  result.width = result.right - result.left;
815cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  result.height = result.bottom - result.top;
816cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  return /** @type {ClientRect} */(result);
817cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)};
818cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
819cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)/**
820cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * Convenience method to create an element of type DIV, give it
821cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * particular class name, and add it as a child of a given parent.
822cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @param {Element} parent The parent element of the new div.
823cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @param {string} className The class name of the new div.
824cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @param {Node=} opt_before Will insert before this node, if present.
825cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @return {Element} The new div.
826cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @private
827cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) */
828cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)cvox.ActiveIndicator.prototype.createDiv_ = function(
829cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      parent, className, opt_before) {
830cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  var elem = document.createElement('div');
8315f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)  elem.setAttribute('aria-hidden', 'true');
8325f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
8335f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)  // This allows the MutationObserver used for live regions to quickly
8345f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)  // ignore changes to this element rather than doing a lot of calculations
8355f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)  // first.
8365f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)  elem.setAttribute('cvoxIgnore', '');
8375f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
838cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  elem.className = className;
839cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  if (opt_before) {
840cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    parent.insertBefore(elem, opt_before);
841cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  } else {
842cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    parent.appendChild(elem);
843cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  }
844cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  return elem;
845cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)};
846cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
847cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)/**
848cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * In WebKit, when the user has zoomed the page, every CSS coordinate is
849cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * multiplied by the zoom level and rounded down. This can cause objects to
850cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * fail to line up; for example an object with left position 100 and width
851cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * 50 may not line up with an object with right position 150 pixels, if the
852cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * zoom is not equal to 1.0. To fix this, we compute the actual desired
853cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * coordinate when zoomed, then add a small fractional offset and divide
854cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * by the zoom factor, and use that value as the item's coordinate instead.
855cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) *
856cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @param {number} x A coordinate to be transformed.
857cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @return {number} The new coordinate to use.
858cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @private
859cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) */
860cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)cvox.ActiveIndicator.prototype.fixZoom_ = function(x) {
861cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  return (Math.round(x * this.zoom_) + 0.1) / this.zoom_;
862cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)};
863cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
864cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)/**
865cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * See fixZoom_, above. This method is the same except that it returns the
866cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * width such that right pos (x + width) is correct when multiplied by the
867cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * zoom factor.
868cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) *
869cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @param {number} x A coordinate to be transformed.
870cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @param {number} width The width of the object.
871cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @return {number} The new width to use.
872cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @private
873cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) */
874cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)cvox.ActiveIndicator.prototype.fixZoomSum_ = function(x, width) {
875cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  var zoomedX = Math.round(x * this.zoom_);
876cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  var zoomedRight = Math.round((x + width) * this.zoom_);
877cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  var zoomedWidth = (zoomedRight - zoomedX);
878cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  return (zoomedWidth + 0.1) / this.zoom_;
879cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)};
880cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
881cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)/**
882cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * Set the coordinates of an element to the given left, top, right, and
883cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * bottom pixel coordinates, taking the browser zoom level into account.
884cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * Also set the clipping rectangle to exclude some of the edges of the
885cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * rectangle, based on the value of showLeft, showTop, showRight, and
886cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * showBottom.
887cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) *
888cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @param {Element} element The element to move.
889cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @param {number} left The new left coordinate.
890cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @param {number} top The new top coordinate.
891cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @param {number} right The new right coordinate.
892cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @param {number} bottom The new bottom coordinate.
893cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @param {boolean} showLeft Whether to show or clip at the left border.
894cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @param {boolean} showTop Whether to show or clip at the top border.
895cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @param {boolean} showRight Whether to show or clip at the right border.
896cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @param {boolean} showBottom Whether to show or clip at the bottom border.
897cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @private
898cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) */
899cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)cvox.ActiveIndicator.prototype.setElementCoords_ = function(
900cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      element,
901cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      left, top, right, bottom,
902cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      showLeft, showTop, showRight, showBottom) {
903cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  var origWidth = right - left;
904cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  var origHeight = bottom - top;
905cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
906cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  var width = right - left;
907cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  var height = bottom - top;
908cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  var clipLeft = showLeft ? -20 : 0;
909cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  var clipTop = showTop ? -20 : 0;
910cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  var clipRight = showRight ? 20 : 0;
911cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  var clipBottom = showBottom ? 20 : 0;
912cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  if (width == 0) {
913cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    if (showRight) {
914cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      left -= 5;
915cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      width += 5;
916cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    } else if (showLeft) {
917cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      width += 10;
918cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    }
919cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    clipTop = 10;
920cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    clipBottom = 10;
921cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    top -= 10;
922cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    height += 20;
923cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  }
924cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  if (!showBottom)
925cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    height += 5;
926cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  if (!showTop) {
927cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    top -= 5;
928cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    height += 5;
929cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    clipTop += 5;
930cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    clipBottom += 5;
931cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  }
932cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  if (clipRight == 0 && origWidth == 0) {
933cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    clipRight = 1;
934cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  } else {
935cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    clipRight = this.fixZoomSum_(left, clipRight + origWidth);
936cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  }
937cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  clipBottom = this.fixZoomSum_(top, clipBottom + origHeight);
938cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
939cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  element.style.left = this.fixZoom_(left) + 'px';
940cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  element.style.top = this.fixZoom_(top) + 'px';
941cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  element.style.width = this.fixZoomSum_(left, width) + 'px';
942cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  element.style.height = this.fixZoomSum_(top, height) + 'px';
943cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  element.style.clip =
944cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      'rect(' + [clipTop, clipRight, clipBottom, clipLeft].join('px ') + 'px)';
945cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)};
946cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
947cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)/**
948cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * Same as setElementCoords_, but takes a rect instead of coordinates.
949cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) *
950cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @param {Element} element The element to move.
951cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @param {ClientRect} r The new coordinates.
952cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @param {boolean} showLeft Whether to show or clip at the left border.
953cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @param {boolean} showTop Whether to show or clip at the top border.
954cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @param {boolean} showRight Whether to show or clip at the right border.
955cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @param {boolean} showBottom Whether to show or clip at the bottom border.
956cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @private
957cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) */
958cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)cvox.ActiveIndicator.prototype.setElementRect_ = function(
959cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      element, r, showLeft, showTop, showRight, showBottom) {
960cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  this.setElementCoords_(element, r.left, r.top, r.right, r.bottom,
961cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)                         showLeft, showTop, showRight, showBottom);
962cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)};
963cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
964cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)/**
965cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * Compute an approximation of the current browser zoom level by
966cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * comparing the measurement of a large character of text
967cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * with the -webkit-text-size-adjust:none style to the expected
968cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * pixel coordinates if it was adjusted.
969cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) * @private
970cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) */
971cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)cvox.ActiveIndicator.prototype.computeZoomLevel_ = function() {
972cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  if (window.innerHeight === this.innerHeight_ &&
973cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      window.innerWidth === this.innerWidth_) {
974cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    return;
975cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  }
976cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
977cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  this.innerHeight_ = window.innerHeight;
978cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  this.innerWidth_ = window.innerWidth;
979cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
980cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  var zoomMeasureElement = document.createElement('div');
981cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  zoomMeasureElement.innerHTML = 'X';
982cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  zoomMeasureElement.setAttribute(
983cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      'style',
984cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      'font: 5000px/1em sans-serif !important;' +
985cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)          ' -webkit-text-size-adjust:none !important;' +
986cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)          ' visibility:hidden !important;' +
987cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)          ' left: -10000px !important;' +
988cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)          ' top: -10000px !important;' +
989cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)          ' position:absolute !important;');
990cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  document.body.appendChild(zoomMeasureElement);
991cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
992cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  var zoomLevel = 5000 / zoomMeasureElement.clientHeight;
993cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  var newZoom = Math.round(zoomLevel * 500) / 500;
994cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  if (newZoom > 0.1 && newZoom < 10) {
995cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    this.zoom_ = newZoom;
996cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  }
997cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
998cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  // TODO(dmazzoni): warn or log if the computed zoom is bad?
999cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  zoomMeasureElement.parentNode.removeChild(zoomMeasureElement);
1000cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)};
1001