OLD | NEW |
(Empty) | |
| 1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. |
| 4 |
| 5 /** |
| 6 * @fileoverview Creates a CSS lens for displaying magnified text. |
| 7 */ |
| 8 |
| 9 goog.provide('chromevis.ChromeVisLens'); |
| 10 |
| 11 goog.require('cvox.BuildConfig'); |
| 12 goog.require('cvox.ExtensionBridge'); |
| 13 goog.require('cvox.SelectionUtil'); |
| 14 |
| 15 /** |
| 16 * Constructor for CSS lens. Initializes the lens settings. |
| 17 * @constructor |
| 18 */ |
| 19 chromevis.ChromeVisLens = function() { |
| 20 |
| 21 /** |
| 22 * The current amount of padding (in pixels) between the text |
| 23 * and the sides of the lens |
| 24 * @type {number} |
| 25 * @private |
| 26 */ |
| 27 this.padding_ = 5; |
| 28 |
| 29 /** |
| 30 * The maximum width of the bubble lens (in pixels) |
| 31 * @type {number} |
| 32 * @private |
| 33 */ |
| 34 this.maxBubbleWidth_ = 700; |
| 35 |
| 36 /** |
| 37 * The minimum width of the bubble lens (in pixels) |
| 38 * @type {number} |
| 39 * @private |
| 40 */ |
| 41 this.minBubbleWidth_ = 25; |
| 42 |
| 43 /** |
| 44 * Whether or not the lens is currently displayed |
| 45 * @type {boolean} |
| 46 * @private |
| 47 */ |
| 48 this.isLensDisplayed_ = false; |
| 49 |
| 50 /** |
| 51 * Whether or not the lens is currently centered |
| 52 * @type {boolean} |
| 53 */ |
| 54 this.isCentered = true; |
| 55 |
| 56 /** |
| 57 * The current magnification multiplier |
| 58 * @type {number} |
| 59 */ |
| 60 this.multiplier = 1.5; |
| 61 |
| 62 /** |
| 63 * The current text color |
| 64 * @type {string} |
| 65 */ |
| 66 this.textColor = '#FFFFFF'; |
| 67 |
| 68 /** |
| 69 * The current lens background color |
| 70 * @type {string} |
| 71 */ |
| 72 this.bgColor = '#000000'; |
| 73 |
| 74 /** |
| 75 * Whether the lens is currently anchored to the top of the page |
| 76 * @type {boolean} |
| 77 */ |
| 78 this.isAnchored = true; |
| 79 |
| 80 /** |
| 81 * The current ChromeVis lens object |
| 82 * @type {Element} |
| 83 */ |
| 84 this.lens = chromevis.ChromeVisLens.ACTIVE_DOC.createElement('span'); |
| 85 |
| 86 this.initializeLens_(); |
| 87 }; |
| 88 |
| 89 |
| 90 /** |
| 91 * The name of the special div that contains settings specified by the |
| 92 * background page |
| 93 * @type {string} |
| 94 * @const |
| 95 */ |
| 96 chromevis.ChromeVisLens.EL_ID = 'chromeVisBackground2LensDiv'; |
| 97 |
| 98 /** |
| 99 * The name of the attribute specifying whether the lens is centered |
| 100 * @type {string} |
| 101 * @const |
| 102 */ |
| 103 chromevis.ChromeVisLens.CENTER_ATTRB = 'data-isCentered'; |
| 104 |
| 105 /** |
| 106 * The name of the attribute specifying the lens magnification |
| 107 * @type {string} |
| 108 * @const |
| 109 */ |
| 110 chromevis.ChromeVisLens.MULT_ATTRB = 'data-textMag'; |
| 111 |
| 112 /** |
| 113 * The name of the attribute specifying the lens text color |
| 114 * @type {string} |
| 115 * @const |
| 116 */ |
| 117 chromevis.ChromeVisLens.TXT_COLOR_ATTRB = 'data-textColor'; |
| 118 |
| 119 /** |
| 120 * The name of the attribute specifying the lens background color |
| 121 * @type {string} |
| 122 * @const |
| 123 */ |
| 124 chromevis.ChromeVisLens.BG_COLOR_ATTRB = 'data-bgColor'; |
| 125 |
| 126 /** |
| 127 * The name of the attribute specifying whether the lens is anchored |
| 128 * @type {string} |
| 129 * @const |
| 130 */ |
| 131 chromevis.ChromeVisLens.ANCHOR_ATTRB = 'data-isAnchored'; |
| 132 |
| 133 /** |
| 134 * The active document |
| 135 * @type {Document} |
| 136 * @const |
| 137 */ |
| 138 chromevis.ChromeVisLens.ACTIVE_DOC = window.document; |
| 139 |
| 140 |
| 141 /** |
| 142 * Initializes the ChromeVis lens with settings pulled from background page. |
| 143 * @private |
| 144 */ |
| 145 chromevis.ChromeVisLens.prototype.initializeLens_ = function() { |
| 146 this.initializeLensCSS_(); |
| 147 |
| 148 this.lens.style.display = 'none'; |
| 149 chromevis.ChromeVisLens.ACTIVE_DOC.body.appendChild(this.lens); |
| 150 |
| 151 this.setupMessageListener_(); |
| 152 |
| 153 this.updateAnchorLens(); |
| 154 }; |
| 155 |
| 156 |
| 157 /** |
| 158 * Listens for an event fired from the extension that indicates the background |
| 159 * page is requesting the lens to update. This event was dispatched to a known |
| 160 * div in the shared DOM (chromeVisBackground2LensDiv) and the div has |
| 161 * attributes known to the lens that contain lens setting information. The |
| 162 * lens reads information from the div and then updates appropriately. |
| 163 * @private |
| 164 */ |
| 165 chromevis.ChromeVisLens.prototype.setupMessageListener_ = function() { |
| 166 if (BUILD_TYPE != BUILD_TYPE_CHROME) { |
| 167 return; |
| 168 } |
| 169 var self = this; |
| 170 cvox.ExtensionBridge.addMessageListener(function(message, port) { |
| 171 switch (message.data) { |
| 172 case 'data-isAnchored': |
| 173 self.setAnchoredLens(message.value); |
| 174 self.isAnchored ? self.updateAnchorLens() : self.updateBubbleLens(); |
| 175 break; |
| 176 case 'data-isCentered': |
| 177 self.isCentered = message.value; |
| 178 if (!self.isAnchored) { |
| 179 self.updateBubbleLens(); |
| 180 } |
| 181 break; |
| 182 case 'data-textMag': |
| 183 var multData = message.value; |
| 184 if (multData != null) { |
| 185 self.multiplier = parseFloat(multData); |
| 186 self.setMagnification(); |
| 187 // Must update position of lens after text size has changed |
| 188 self.isAnchored ? self.updateAnchorLens() : self.updateBubbleLens(); |
| 189 } |
| 190 break; |
| 191 case 'data-textColor': |
| 192 var textColorData = message.value; |
| 193 if (textColorData != null) { |
| 194 self.textColor = textColorData; |
| 195 self.setTextColor(); |
| 196 } |
| 197 break; |
| 198 case 'data-bgColor': |
| 199 var bgColorData = message.value; |
| 200 if (bgColorData != null) { |
| 201 self.bgColor = bgColorData; |
| 202 self.setBgColor(); |
| 203 } |
| 204 break; |
| 205 } |
| 206 }); |
| 207 }; |
| 208 |
| 209 |
| 210 /** |
| 211 * Displays or hides the lens. |
| 212 * @param {boolean} show Whether or not the lens should be shown. |
| 213 */ |
| 214 chromevis.ChromeVisLens.prototype.showLens = function(show) { |
| 215 show ? this.lens.style.display = 'block' : |
| 216 this.lens.style.display = 'none'; |
| 217 |
| 218 this.isLensDisplayed_ = show; |
| 219 |
| 220 this.isLensDisplayed_ ? this.updateText() : document.body.style.marginTop = 0; |
| 221 }; |
| 222 |
| 223 |
| 224 /** |
| 225 * Initializes the lens CSS. |
| 226 * @private |
| 227 */ |
| 228 chromevis.ChromeVisLens.prototype.initializeLensCSS_ = function() { |
| 229 this.lens.style.backgroundColor = this.bgColor; |
| 230 |
| 231 // Style settings |
| 232 this.lens.style.borderColor = '#000000'; |
| 233 this.lens.style.borderWidth = 'medium'; |
| 234 this.lens.style.borderStyle = 'groove'; |
| 235 |
| 236 this.lens.style.position = 'absolute'; |
| 237 |
| 238 // Note: there is no specified maximum value for the zIndex. |
| 239 // Occasionally there will be a website that has an element with a zIndex |
| 240 // higher than this one. The only fix is to manually go here and increase |
| 241 // the zIndex. |
| 242 this.lens.style.zIndex = 100000000000; |
| 243 |
| 244 this.lens.style.minHeight = 5 + 'px'; |
| 245 |
| 246 this.lens.style.webkitBorderRadius = '7px'; |
| 247 |
| 248 // Class setting - this special class name means ChromeVis will |
| 249 // not try to select text content inside the lens. |
| 250 this.lens.className = cvox.TraverseUtil.SKIP_CLASS; |
| 251 }; |
| 252 |
| 253 |
| 254 /** |
| 255 * Sets whether the lens is anchored to the top of the page or whether it floats |
| 256 * near the selected text. |
| 257 * @param {boolean} anchored Whether or not the lens is anchored. |
| 258 */ |
| 259 chromevis.ChromeVisLens.prototype.setAnchoredLens = function(anchored) { |
| 260 this.isAnchored = anchored; |
| 261 if ((this.isLensDisplayed_) && (!this.isAnchored)) { |
| 262 document.body.style.marginTop = 0; |
| 263 } |
| 264 }; |
| 265 |
| 266 |
| 267 /** |
| 268 * Refreshes the position of the anchor lens on the page. This is usually done |
| 269 * in response to scrolling or a window resize. |
| 270 */ |
| 271 chromevis.ChromeVisLens.prototype.updateAnchorLens = function() { |
| 272 this.lens.style.top = window.scrollY + 'px'; |
| 273 this.lens.style.minWidth = (window.innerWidth - 50) + 'px'; |
| 274 this.lens.style.maxWidth = (window.innerWidth - 50) + 'px'; |
| 275 |
| 276 this.lens.style.left = 10 + 'px'; |
| 277 this.lens.style.right = 100 + 'px'; |
| 278 |
| 279 // Push rest of document down underneath anchor lens. |
| 280 // Does not work with documents that have absolutely positioned |
| 281 // elements - because absolutely positioned element margins |
| 282 // never collapse with global margins. |
| 283 |
| 284 var bod = document.body; |
| 285 // need to add 10 to the computed style to take into account the margin |
| 286 var str_ht = window.getComputedStyle(this.lens, null).height; |
| 287 var ht = parseInt(str_ht.substr(0, str_ht.length - 2), 10) + 20; |
| 288 bod.style.marginTop = ht + 'px'; |
| 289 }; |
| 290 |
| 291 |
| 292 /** |
| 293 * Refreshes the position of the bubble lens on the page. This is done in |
| 294 * response to the selection changing or the window resizing. |
| 295 */ |
| 296 chromevis.ChromeVisLens.prototype.updateBubbleLens = function() { |
| 297 var sel = window.getSelection(); |
| 298 var pos = cvox.SelectionUtil.findSelPosition(sel); |
| 299 |
| 300 var top; |
| 301 var left; |
| 302 if (pos == null) { |
| 303 top = 0; |
| 304 left = 0; |
| 305 } |
| 306 top = pos[0]; |
| 307 left = pos[1]; |
| 308 |
| 309 this.lens.style.minWidth = 0; |
| 310 |
| 311 // Calculate maximum lens width |
| 312 var parent; |
| 313 var maxw; |
| 314 if (this.isCentered) { |
| 315 // Want width with lens centered in the parent element |
| 316 // So maxwidth is width of parent |
| 317 parent = sel.getRangeAt(0).commonAncestorContainer; |
| 318 while (!parent.offsetWidth) { |
| 319 parent = parent.parentNode; |
| 320 } |
| 321 maxw = Math.min(this.maxBubbleWidth_, parent.offsetWidth); |
| 322 } else { |
| 323 // Align the left edge of the lens with the left edge of the selection |
| 324 // So find maxwidth with left edge aligned |
| 325 maxw = Math.min(this.maxBubbleWidth_, |
| 326 ((document.body.clientWidth - left) - 16)); |
| 327 } |
| 328 |
| 329 this.lens.style.maxWidth = maxw + 'px'; |
| 330 // Now calculate lens left position |
| 331 // First check if actual width is larger than maxWidth |
| 332 if (this.lens.firstChild.scrollWidth > maxw) { |
| 333 var shiftLeft = this.lens.firstChild.scrollWidth - maxw; |
| 334 |
| 335 this.lens.style.maxWidth = this.lens.firstChild.scrollWidth + 'px'; |
| 336 this.lens.style.left = (left - shiftLeft) + 'px'; |
| 337 } else { |
| 338 if (this.isCentered) { |
| 339 // Center the lens in the parent element |
| 340 var pleft = 0; |
| 341 var obj = parent; |
| 342 |
| 343 if (obj.offsetParent) { |
| 344 pleft = obj.offsetLeft; |
| 345 obj = obj.offsetParent; |
| 346 while (obj !== null) { |
| 347 pleft += obj.offsetLeft; |
| 348 obj = obj.offsetParent; |
| 349 } |
| 350 } |
| 351 |
| 352 this.lens.style.left = |
| 353 Math.ceil((pleft + (parent.offsetWidth / 2)) - |
| 354 (this.lens.firstChild.scrollWidth / 2)) + |
| 355 'px'; |
| 356 } else { |
| 357 // Align the left edge of the lens with the left edge of the selection |
| 358 this.lens.style.left = left + 'px'; |
| 359 } |
| 360 } |
| 361 this.lens.style.right = 'auto'; |
| 362 |
| 363 // Calculate lens height and top position |
| 364 var str_ht = window.getComputedStyle(this.lens, null).height; |
| 365 var ht = parseInt(str_ht.substr(0, str_ht.length - 2), 10) + 20; |
| 366 |
| 367 var actualTop = top - ht; |
| 368 if (actualTop < 0) { |
| 369 this.lens.style.top = 5 + 'px'; |
| 370 } else { |
| 371 this.lens.style.top = actualTop + 'px'; |
| 372 } |
| 373 }; |
| 374 |
| 375 |
| 376 /** |
| 377 * Update the text displayed inside the lens. This is done in response to the |
| 378 * selection changing. |
| 379 */ |
| 380 chromevis.ChromeVisLens.prototype.updateText = function() { |
| 381 if (this.isLensDisplayed_) { |
| 382 // Need to replace current lens node due to Webkit caching some |
| 383 // display settings between lens changes. |
| 384 chromevis.ChromeVisLens.ACTIVE_DOC = window.document; |
| 385 chromevis.ChromeVisLens.ACTIVE_DOC.body.removeChild(this.lens); |
| 386 this.lens = chromevis.ChromeVisLens.ACTIVE_DOC.createElement('span'); |
| 387 |
| 388 this.initializeLensCSS_(); |
| 389 |
| 390 chromevis.ChromeVisLens.ACTIVE_DOC.body.appendChild(this.lens); |
| 391 |
| 392 var sel = window.getSelection(); |
| 393 var selectedText = sel.toString(); |
| 394 |
| 395 if (selectedText == null) { |
| 396 // No selection, but still need to update the lens so it |
| 397 // has a consistent appearance |
| 398 selectedText = ''; |
| 399 } |
| 400 |
| 401 while (this.lens.firstChild) { |
| 402 this.lens.removeChild(this.lens.firstChild); |
| 403 } |
| 404 |
| 405 var clonedNode = document.createElement('div'); |
| 406 |
| 407 // To preserve new lines in selected text, need to create child div nodes |
| 408 // of the new element. |
| 409 // This also guards against selected text that includes HTML tags. |
| 410 var newSelectedText = ''; |
| 411 var childNode = document.createElement('div'); |
| 412 |
| 413 for (var i = 0; i < selectedText.length; i++) { |
| 414 if ((selectedText.charCodeAt(i) == 10)) { |
| 415 childNode.textContent = newSelectedText; |
| 416 clonedNode.appendChild(childNode); |
| 417 childNode = document.createElement('div'); |
| 418 newSelectedText = ''; |
| 419 } else { |
| 420 newSelectedText = newSelectedText + selectedText.charAt(i); |
| 421 } |
| 422 } |
| 423 childNode.textContent = newSelectedText; |
| 424 clonedNode.appendChild(childNode); |
| 425 |
| 426 // Style settings |
| 427 clonedNode.style.fontFamily = 'Verdana, Arial, Helvetica, sans-serif'; |
| 428 clonedNode.style.fontWeight = 'normal'; |
| 429 clonedNode.style.fontStyle = 'normal'; |
| 430 clonedNode.style.color = this.textColor; |
| 431 clonedNode.style.textDecoration = 'none'; |
| 432 clonedNode.style.textAlign = 'left'; |
| 433 clonedNode.style.lineHeight = 1.2; |
| 434 |
| 435 this.lens.appendChild(clonedNode); |
| 436 |
| 437 this.magnifyText_(); |
| 438 this.padText_(); |
| 439 this.isAnchored ? this.updateAnchorLens() : this.updateBubbleLens(); |
| 440 } |
| 441 }; |
| 442 |
| 443 |
| 444 /** |
| 445 * Updates the lens in response to a window resize. |
| 446 */ |
| 447 chromevis.ChromeVisLens.prototype.updateResized = function() { |
| 448 this.isAnchored ? this.updateAnchorLens() : this.updateBubbleLens(); |
| 449 }; |
| 450 |
| 451 |
| 452 /** |
| 453 * Updates the lens in response to the document being scrolled; |
| 454 */ |
| 455 chromevis.ChromeVisLens.prototype.updateScrolled = function() { |
| 456 if (this.isAnchored) { |
| 457 this.updateAnchorLens(); |
| 458 |
| 459 if (this.isLensDisplayed_) { |
| 460 // Force redraw to counteract scroll acceleration problem |
| 461 // Workaround: hide lens, check offsetHeight, display lens |
| 462 // this forces a redraw |
| 463 // TODO: file Chrome bug, get rid of this |
| 464 this.lens.style.display = 'none'; |
| 465 var redrawFix = this.lens.offsetHeight; |
| 466 this.lens.style.display = 'block'; |
| 467 } |
| 468 } |
| 469 }; |
| 470 |
| 471 |
| 472 /** |
| 473 * Adjusts the text magnification. |
| 474 * @private |
| 475 */ |
| 476 chromevis.ChromeVisLens.prototype.magnifyText_ = function() { |
| 477 var adjustment = (this.multiplier * 100) + '%'; |
| 478 |
| 479 if (this.lens.firstChild) { |
| 480 if (this.lens.firstChild.hasChildNodes()) { |
| 481 var children = this.lens.firstChild.childNodes; |
| 482 |
| 483 for (var i = 0; i < children.length; i++) { |
| 484 children[i].style.fontSize = adjustment; |
| 485 } |
| 486 } |
| 487 } |
| 488 }; |
| 489 |
| 490 |
| 491 /** |
| 492 * Adjusts the padding around the text inside the lens. |
| 493 * @private |
| 494 */ |
| 495 chromevis.ChromeVisLens.prototype.padText_ = function() { |
| 496 if (this.padding_ < 0) { |
| 497 return; |
| 498 } |
| 499 this.lens.style.padding_ = this.padding_ + 'px'; |
| 500 }; |
| 501 |
| 502 |
| 503 /** |
| 504 * Sets the text magnification multiplier. |
| 505 */ |
| 506 chromevis.ChromeVisLens.prototype.setMagnification = function() { |
| 507 this.magnifyText_(); |
| 508 this.padText_(); |
| 509 }; |
| 510 |
| 511 |
| 512 /** |
| 513 * Returns the current text size multiplier for magnification. |
| 514 * @return {number} The current text size multiplier. |
| 515 */ |
| 516 chromevis.ChromeVisLens.prototype.getMagnification = function() { |
| 517 return this.multiplier; |
| 518 }; |
| 519 |
| 520 |
| 521 /** |
| 522 * Returns the current background color. |
| 523 * @return {string} The lens background color. |
| 524 */ |
| 525 chromevis.ChromeVisLens.prototype.getBgColor = function() { |
| 526 return this.bgColor; |
| 527 }; |
| 528 |
| 529 |
| 530 /** |
| 531 * Updates the lens background color. |
| 532 */ |
| 533 chromevis.ChromeVisLens.prototype.setBgColor = function() { |
| 534 this.lens.style.backgroundColor = this.bgColor; |
| 535 }; |
| 536 |
| 537 |
| 538 /** |
| 539 * Returns the current text color. |
| 540 * @return {string} The lens text color. |
| 541 */ |
| 542 chromevis.ChromeVisLens.prototype.getTextColor = function() { |
| 543 return this.textColor; |
| 544 }; |
| 545 |
| 546 |
| 547 /** |
| 548 * Updates the lens text color. |
| 549 */ |
| 550 chromevis.ChromeVisLens.prototype.setTextColor = function() { |
| 551 if (this.lens.firstChild) { |
| 552 this.lens.firstChild.style.color = this.textColor; |
| 553 } |
| 554 }; |
OLD | NEW |