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