| 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 goog.provide('cvox.GmailApi'); |
| 6 |
| 7 goog.require('cvox.ChromeVox'); |
| 8 |
| 9 /** |
| 10 * @fileoverview API for accessing GMail content and getting events from GMail. |
| 11 * The API exposes an event driven API for accessibility. Having this API keeps |
| 12 * the accessibility scripts from being dependent on the inner workings of |
| 13 * GMail. |
| 14 */ |
| 15 var GmailApi = { }; |
| 16 |
| 17 /** |
| 18 * Collection of classnames. Used to check on the status of threads and |
| 19 * messages. |
| 20 * @private |
| 21 * @type {Object.<String, String>} |
| 22 */ |
| 23 GmailApi.CLASSES_ = { |
| 24 MESSAGE_STARRED: 'f g9', |
| 25 MESSAGE_NOT_STARRED: 'f g8', |
| 26 THREAD_STARRED: 'W5RYx', |
| 27 THREAD_NOT_STARRED: 'EqK8f', |
| 28 UNREAD_SELECTED: 'zA zE x7', |
| 29 UNREAD_NOT_SELECTED: 'zA zE', |
| 30 READ_SELECTED: 'zA yO x7', |
| 31 READ_NOT_SELECTED: 'zA yO', |
| 32 MESSAGE_EXPANDED: 'h7', |
| 33 BUTTERBAR_NOTIFICATION: 'vh' |
| 34 }; |
| 35 |
| 36 /** |
| 37 * @private |
| 38 * @type {Element} |
| 39 */ |
| 40 GmailApi.previousThreadNode_ = null; |
| 41 |
| 42 /** |
| 43 * @private |
| 44 * @type {Element} |
| 45 */ |
| 46 GmailApi.previousMessageNode_ = null; |
| 47 |
| 48 /** |
| 49 * @private |
| 50 * @type {Element} |
| 51 */ |
| 52 GmailApi.previousSearchCompletionNode_ = null; |
| 53 |
| 54 /** |
| 55 * Callback listener to invoke when an event happens. |
| 56 * @private |
| 57 * @type {Function} |
| 58 */ |
| 59 GmailApi.listener_ = null; |
| 60 |
| 61 /** |
| 62 * Initializes the Gmail script. |
| 63 * @private |
| 64 */ |
| 65 GmailApi.init_ = function() { |
| 66 window.addEventListener('keyup', GmailApi.keyupHandler_, true); |
| 67 window.addEventListener('DOMSubtreeModified', |
| 68 GmailApi.domSubtreeModified_, true); |
| 69 }; |
| 70 |
| 71 /** |
| 72 * Sets the GmailListener object to be used for callbacks. |
| 73 * |
| 74 * public: |
| 75 * @param {function} gmailListener The GmailListener to be used. |
| 76 */ |
| 77 GmailApi.setGmailEventListener = function(gmailListener) { |
| 78 GmailApi.listener_ = gmailListener; |
| 79 }; |
| 80 |
| 81 /** |
| 82 * Gets the current GmailThread if it has changed since the last time |
| 83 * this function was called. |
| 84 * |
| 85 * @private |
| 86 * @return {Object} The current GmailThread if it has changed. Returns |
| 87 * null otherwise. |
| 88 */ |
| 89 GmailApi.getCurrentThreadIfChanged_ = function() { |
| 90 var currentThread = GmailApi.getCurrentThread(); |
| 91 if ((currentThread != null) && |
| 92 (currentThread.getNode() != GmailApi.previousThreadNode_)) { |
| 93 GmailApi.previousThreadNode_ = currentThread.getNode(); |
| 94 return currentThread; |
| 95 } |
| 96 return null; |
| 97 }; |
| 98 |
| 99 /** |
| 100 * Returns the current GmailThread with the chevron arrow pointed at it. |
| 101 * |
| 102 * public: |
| 103 * @return {Object} The current GmailThread object. |
| 104 */ |
| 105 GmailApi.getCurrentThread = function() { |
| 106 var chevronNodes = document.getElementsByClassName('oZ-jd'); |
| 107 if (!chevronNodes) { |
| 108 return null; |
| 109 } |
| 110 // Start from the end of the list and work backwards since GMail keeps |
| 111 // the previous chevron nodes and the style for those are still 'visible'. |
| 112 for (var node, i = chevronNodes.length - 1; node = chevronNodes[i]; i--) { |
| 113 if (node.style.visibility == 'visible') { |
| 114 var currentThread = node.parentNode.parentNode; |
| 115 var starred = false; |
| 116 var selected = false; |
| 117 var read = false; |
| 118 // TODO: These checks could come from a GMail function to return the |
| 119 // status info. |
| 120 // Check if the current thread is selected |
| 121 if ((currentThread.className == GmailApi.CLASSES_.READ_SELECTED) || |
| 122 (currentThread.className == GmailApi.CLASSES_.UNREAD_SELECTED)) { |
| 123 selected = true; |
| 124 } |
| 125 // Check if the current thread is starred. |
| 126 // Note that Gmail has an inconsistency in its alt text. If a thread is |
| 127 // starred when Gmail is loaded, it will have alt text "starred". But |
| 128 // if it is starred/unstarred later, the alt text is not dynamically |
| 129 // changed. |
| 130 if (currentThread.getElementsByClassName( |
| 131 GmailApi.CLASSES_.STARRED).length > 0) { |
| 132 starred = true; |
| 133 currentThread.getElementsByClassName( |
| 134 GmailApi.CLASSES_.STARRED)[0].alt = ''; |
| 135 } else if (currentThread.getElementsByClassName( |
| 136 GmailApi.CLASSES_.NOT_STARRED).length > 0) { |
| 137 currentThread.getElementsByClassName( |
| 138 GmailApi.CLASSES_.NOT_STARRED)[0].alt = ''; |
| 139 } |
| 140 // Check if the current thread is read |
| 141 if ((currentThread.className == GmailApi.CLASSES_.READ_SELECTED) || |
| 142 (currentThread.className == GmailApi.CLASSES_.READ_NOT_SELECTED)) { |
| 143 read = true; |
| 144 } |
| 145 return new GmailThread(currentThread, starred, selected, read); |
| 146 } |
| 147 } |
| 148 return null; |
| 149 }; |
| 150 |
| 151 /** |
| 152 * Gets the current message if it has changed since the last time |
| 153 * this function was called. |
| 154 * |
| 155 * @private |
| 156 * @return {GmailMessage?} The current message if it has changed. Returns |
| 157 * null otherwise. |
| 158 */ |
| 159 GmailApi.getCurrentMessageIfChanged_ = function() { |
| 160 var currentMessage = GmailApi.getCurrentMessage(); |
| 161 if ((currentMessage != null) && |
| 162 (currentMessage.getNode() != GmailApi.previousMessageNode_)) { |
| 163 GmailApi.previousMessageNode_ = currentMessage.getNode(); |
| 164 return currentMessage; |
| 165 } |
| 166 return null; |
| 167 }; |
| 168 |
| 169 /** |
| 170 * Returns the current message node with the chevron arrow pointed at it. |
| 171 * |
| 172 * public: |
| 173 * @return {GmailMessage?} The current message node. |
| 174 */ |
| 175 GmailApi.getCurrentMessage = function() { |
| 176 var chevronNodes = document.getElementsByClassName('hF'); |
| 177 var actualMessageNode = null; |
| 178 if (chevronNodes) { |
| 179 for (var node, i = 0; node = chevronNodes[i]; i++) { |
| 180 if (node.className == 'hF') { |
| 181 if (actualMessageNode) { |
| 182 if (node.parentNode.textContent.length > |
| 183 actualMessageNode.textContent.length) { |
| 184 actualMessageNode = node.parentNode; |
| 185 } |
| 186 } else { |
| 187 actualMessageNode = node.parentNode; |
| 188 } |
| 189 } |
| 190 } |
| 191 } |
| 192 var gmailMessage; |
| 193 if (actualMessageNode != null) { |
| 194 // TODO: Get the actual starred/not starred status. |
| 195 // This hack of always setting the starred status to |
| 196 // false happens to work here since we are reading the |
| 197 // entire message node and the alt text for the star status |
| 198 // is changed correctly on messages. |
| 199 gmailMessage = new GmailMessage(actualMessageNode, false); |
| 200 } |
| 201 return gmailMessage; |
| 202 }; |
| 203 |
| 204 /** |
| 205 * Gets the current search completion if it has changed since the last time |
| 206 * this function was called. |
| 207 * |
| 208 * @private |
| 209 * @return {GmailSearchCompletion?} The current search completion if it has |
| 210 * changed. Returns null otherwise. |
| 211 */ |
| 212 GmailApi.getCurrentSearchCompletionIfChanged_ = function() { |
| 213 var currentSearchCompletion = GmailApi.getCurrentSearchCompletion(); |
| 214 if ((currentSearchCompletion != null) && |
| 215 (currentSearchCompletion.getNode() != |
| 216 GmailApi.previousSearchCompletionNode_)) { |
| 217 GmailApi.previousSearchCompletionNode_ = currentSearchCompletion.getNode(); |
| 218 return currentSearchCompletion; |
| 219 } |
| 220 return null; |
| 221 }; |
| 222 |
| 223 /** |
| 224 * Returns the current search completion node that is highlighted. |
| 225 * |
| 226 * public: |
| 227 * @return {Element?} The current search completion node. |
| 228 */ |
| 229 GmailApi.getCurrentSearchCompletion = function() { |
| 230 var currentSearchCompletionNode = null; |
| 231 var highlightedCompletionNodes = |
| 232 document.getElementsByClassName('aq Jd-Je Je'); |
| 233 if (highlightedCompletionNodes && (highlightedCompletionNodes.length > 0)) { |
| 234 currentSearchCompletionNode = highlightedCompletionNodes[0]; |
| 235 } |
| 236 var gmailSearchCompletion; |
| 237 if (currentSearchCompletionNode != null) { |
| 238 gmailSearchCompletion = new GmailSearchCompletion( |
| 239 currentSearchCompletionNode); |
| 240 } |
| 241 return gmailSearchCompletion; |
| 242 }; |
| 243 |
| 244 /** |
| 245 * Catches DOM mutation events which indicate if a thread/message has been |
| 246 * starred, selected, etc. and dispatches these status change events to |
| 247 * the GmailListener. |
| 248 * |
| 249 * @private |
| 250 * @param {Event} evt The DOMSubtreeModified event used to determine when |
| 251 * an interesting status change has occurred. |
| 252 */ |
| 253 GmailApi.domSubtreeModified_ = function(evt) { |
| 254 var target = evt.target; |
| 255 if (target.className) { |
| 256 // TODO: These checks could come from a GMail function to return the status |
| 257 // info. |
| 258 switch (target.className) { |
| 259 case GmailApi.CLASSES_.MESSAGE_STARRED: |
| 260 GmailApi.listener_.onStatusChangeEvent(GmailListener.STATUS.Starred, |
| 261 ''); |
| 262 break; |
| 263 case GmailApi.CLASSES_.MESSAGE_NOT_STARRED: |
| 264 GmailApi.listener_.onStatusChangeEvent(GmailListener.STATUS.NotStarred, |
| 265 ''); |
| 266 break; |
| 267 case GmailApi.CLASSES_.THREAD_STARRED: |
| 268 GmailApi.listener_.onStatusChangeEvent(GmailListener.STATUS.Starred, |
| 269 ''); |
| 270 break; |
| 271 case GmailApi.CLASSES_.THREAD_NOT_STARRED: |
| 272 GmailApi.listener_.onStatusChangeEvent(GmailListener.STATUS.NotStarred, |
| 273 ''); |
| 274 break; |
| 275 case GmailApi.CLASSES_.READ_SELECTED: |
| 276 GmailApi.listener_.onStatusChangeEvent(GmailListener.STATUS.Selected, |
| 277 ''); |
| 278 break; |
| 279 case GmailApi.CLASSES_.READ_NOT_SELECTED: |
| 280 GmailApi.listener_.onStatusChangeEvent(GmailListener.STATUS.NotSelected, |
| 281 ''); |
| 282 break; |
| 283 case GmailApi.CLASSES_.UNREAD_SELECTED: |
| 284 GmailApi.listener_.onStatusChangeEvent(GmailListener.STATUS.Selected, |
| 285 ''); |
| 286 break; |
| 287 case GmailApi.CLASSES_.UNREAD_NOT_SELECTED: |
| 288 GmailApi.listener_.onStatusChangeEvent(GmailListener.STATUS.NotSelected, |
| 289 ''); |
| 290 break; |
| 291 case GmailApi.CLASSES_.MESSAGE_EXPANDED: |
| 292 var messageObj = null; |
| 293 if (messageObj = GmailApi.getCurrentMessageIfChanged_()) { |
| 294 GmailApi.listener_.onNavigatedToNewMessage(messageObj); |
| 295 } |
| 296 break; |
| 297 case GmailApi.CLASSES_.BUTTERBAR_NOTIFICATION: |
| 298 // Use a timeout here to |
| 299 window.setTimeout(function() { |
| 300 GmailApi.listener_.onStatusChangeEvent( |
| 301 GmailListener.STATUS.Notification, |
| 302 evt.target.firstChild.textContent); |
| 303 }, 100); |
| 304 break; |
| 305 } |
| 306 } |
| 307 }; |
| 308 |
| 309 /** |
| 310 * Checks to see if the user has pressed a key that has caused |
| 311 * navigation to happen, and if so, dispatches the navigation event |
| 312 * to the GmailListener. |
| 313 * |
| 314 * @private |
| 315 * @param {Event} evt The keyup event. This is not actually used. |
| 316 */ |
| 317 GmailApi.keyupHandler_ = function(evt) { |
| 318 // Ignore key events if they somehow involve the ctrl and/or alt keys. |
| 319 if (evt.ctrlKey || evt.altKey || (evt.keyCode == 17) || (evt.keyCode == 18)) { |
| 320 return; |
| 321 } |
| 322 var threadObj = null; |
| 323 if (threadObj = GmailApi.getCurrentThreadIfChanged_()) { |
| 324 GmailApi.listener_.onNavigatedToNewThread(threadObj); |
| 325 } |
| 326 var messageObj = null; |
| 327 if (messageObj = GmailApi.getCurrentMessageIfChanged_()) { |
| 328 GmailApi.listener_.onNavigatedToNewMessage(messageObj); |
| 329 } |
| 330 var searchCompletionObj = null; |
| 331 if (searchCompletionObj = GmailApi.getCurrentSearchCompletionIfChanged_()) { |
| 332 GmailApi.listener_.onNavigatedToNewSearchCompletion(searchCompletionObj); |
| 333 } |
| 334 }; |
| 335 |
| 336 |
| 337 /** |
| 338 * Listener interface for receiving GMail events. |
| 339 * |
| 340 * public: |
| 341 * @constructor |
| 342 */ |
| 343 var GmailListener = function() { |
| 344 }; |
| 345 |
| 346 /** |
| 347 * Collection of status codes. |
| 348 * |
| 349 * public: |
| 350 * @type {Object} |
| 351 */ |
| 352 GmailListener.STATUS = { |
| 353 Starred: 0, |
| 354 NotStarred: 1, |
| 355 Selected: 2, |
| 356 NotSelected: 3, |
| 357 Read: 4, |
| 358 Unread: 5, |
| 359 Notification: 6 |
| 360 }; |
| 361 |
| 362 /** |
| 363 * Called when the user has navigated to a new thread. |
| 364 * |
| 365 * public: |
| 366 * @param {Object} threadObject The GmailThread object. |
| 367 */ |
| 368 GmailListener.prototype.onNavigatedToNewThread = function(threadObject) { |
| 369 }; |
| 370 |
| 371 /** |
| 372 * Called when the user has navigated to a new message. |
| 373 * |
| 374 * public: |
| 375 * @param {Object} messageObject The GmailThread object. |
| 376 */ |
| 377 GmailListener.prototype.onNavigatedToNewMessage = function(messageObject) { |
| 378 }; |
| 379 |
| 380 /** |
| 381 * Called when the user has navigated to a new search completion. |
| 382 * |
| 383 * public: |
| 384 * @param {Object} searchCompletionObject The GmailSearchCompletion object. |
| 385 */ |
| 386 GmailListener.prototype.onNavigatedToNewSearchCompletion = |
| 387 function(searchCompletionObject) { |
| 388 }; |
| 389 |
| 390 |
| 391 /** |
| 392 * Called when there is a status change. |
| 393 * |
| 394 * public: |
| 395 * @param {number} status The current status code. |
| 396 * @param {String?} message Optional string if there is more information. |
| 397 */ |
| 398 GmailListener.prototype.onStatusChangeEvent = function(status, message) { |
| 399 }; |
| 400 |
| 401 /** |
| 402 * GMail thread object that holds the DOM node of the thread + status |
| 403 * information for that thread. |
| 404 * |
| 405 * public: |
| 406 * @constructor |
| 407 * @param {Element} node The node that contains the GmailThread. |
| 408 * @param {boolean} starred Whether or not the GmailThread is starred. |
| 409 * @param {boolean} selected Whether or not the GmailThread is selected. |
| 410 * @param {boolean} read Whether or not the GmailThread is read. |
| 411 */ |
| 412 var GmailThread = function(node, starred, selected, read) { |
| 413 this.node_ = node; |
| 414 this.starred_ = starred; |
| 415 this.selected_ = selected; |
| 416 this.read_ = read; |
| 417 }; |
| 418 |
| 419 /** |
| 420 * Returns the DOM node for the thread. |
| 421 * |
| 422 * public: |
| 423 * @return {Object} The current thread node. |
| 424 */ |
| 425 GmailThread.prototype.getNode = function() { |
| 426 return this.node_; |
| 427 }; |
| 428 |
| 429 /** |
| 430 * Returns whether or not the thread is starred. |
| 431 * |
| 432 * public: |
| 433 * @return {boolean} Whether or not the thread is starred. |
| 434 */ |
| 435 GmailThread.prototype.isStarred = function() { |
| 436 return this.starred_; |
| 437 }; |
| 438 |
| 439 /** |
| 440 * Returns whether or not the thread is selected. |
| 441 * |
| 442 * public: |
| 443 * @return {boolean} Whether or not the thread is selected. |
| 444 */ |
| 445 GmailThread.prototype.isSelected = function() { |
| 446 return this.selected_; |
| 447 }; |
| 448 |
| 449 /** |
| 450 * Returns whether or not the thread is read. |
| 451 * |
| 452 * public: |
| 453 * @return {boolean} Whether or not the thread is read. |
| 454 */ |
| 455 GmailThread.prototype.isRead = function() { |
| 456 return this.read_; |
| 457 }; |
| 458 |
| 459 /** |
| 460 * Returns an array of labels for the thread. |
| 461 * |
| 462 * public: |
| 463 * @return {Object} Array of labels for the thread. |
| 464 */ |
| 465 GmailThread.prototype.getLabels = function() { |
| 466 return new Array(); |
| 467 }; |
| 468 |
| 469 /** |
| 470 * Returns an array of authors for the thread. |
| 471 * |
| 472 * public: |
| 473 * @return {Object} Array of authors for the thread. |
| 474 */ |
| 475 GmailThread.prototype.getAuthors = function() { |
| 476 return new Array(); |
| 477 }; |
| 478 |
| 479 /** |
| 480 * Returns the subject of the thread. |
| 481 * |
| 482 * public: |
| 483 * @return {string} The thread subject. |
| 484 */ |
| 485 GmailThread.prototype.getSubject = function() { |
| 486 return ''; |
| 487 }; |
| 488 |
| 489 /** |
| 490 * Returns the snippet for the thread. |
| 491 * |
| 492 * public: |
| 493 * @return {string} The snippet for the thread. |
| 494 */ |
| 495 GmailThread.prototype.getSnippet = function() { |
| 496 return ''; |
| 497 }; |
| 498 |
| 499 /** |
| 500 * Returns the date/time string for the thread. |
| 501 * |
| 502 * public: |
| 503 * @return {string} The date/time for the thread. |
| 504 */ |
| 505 GmailThread.prototype.getDateTime = function() { |
| 506 return ''; |
| 507 }; |
| 508 |
| 509 /** |
| 510 * GMail message object that holds the DOM node of the message + status |
| 511 * information for that message. |
| 512 * |
| 513 * public: |
| 514 * @constructor |
| 515 * @param {Element} node The node that contains the GmailMessage. |
| 516 * @param {boolean} starred Whether or not the GmailMessage is starred. |
| 517 */ |
| 518 var GmailMessage = function(node, starred) { |
| 519 this.node_ = node; |
| 520 this.starred_ = starred; |
| 521 }; |
| 522 |
| 523 /** |
| 524 * Returns the DOM node for the message. |
| 525 * |
| 526 * public: |
| 527 * @return {Object} The current message node. |
| 528 */ |
| 529 GmailMessage.prototype.getNode = function() { |
| 530 return this.node_; |
| 531 }; |
| 532 |
| 533 /** |
| 534 * Returns whether or not the message is starred. |
| 535 * |
| 536 * public: |
| 537 * @return {boolean} Whether or not the message is starred. |
| 538 */ |
| 539 GmailMessage.prototype.isStarred = function() { |
| 540 return this.starred_; |
| 541 }; |
| 542 |
| 543 /** |
| 544 * Returns whether or not the message is read. |
| 545 * |
| 546 * public: |
| 547 * @return {boolean} Whether or not the message is read. |
| 548 */ |
| 549 GmailMessage.prototype.isRead = function() { |
| 550 return this.read_; |
| 551 }; |
| 552 |
| 553 /** |
| 554 * Returns the author for the message. |
| 555 * |
| 556 * public: |
| 557 * @return {string} The author of the message. |
| 558 */ |
| 559 GmailMessage.prototype.getAuthor = function() { |
| 560 return ''; |
| 561 }; |
| 562 |
| 563 /** |
| 564 * Returns the subject of the message. |
| 565 * |
| 566 * public: |
| 567 * @return {string} The message subject. |
| 568 */ |
| 569 GmailMessage.prototype.getSubject = function() { |
| 570 return ''; |
| 571 }; |
| 572 |
| 573 /** |
| 574 * Returns the content for the message. |
| 575 * |
| 576 * public: |
| 577 * @return {string} The content for the message. |
| 578 */ |
| 579 GmailMessage.prototype.getContent = function() { |
| 580 return ''; |
| 581 }; |
| 582 |
| 583 /** |
| 584 * Returns the date/time string for the message. |
| 585 * |
| 586 * public: |
| 587 * @return {string} The date/time for the message. |
| 588 */ |
| 589 GmailMessage.prototype.getDateTime = function() { |
| 590 return ''; |
| 591 }; |
| 592 |
| 593 /** |
| 594 * Returns the message's GmailThread object. |
| 595 * |
| 596 * public: |
| 597 * @return {GmailThread?} The GmailThread object. |
| 598 */ |
| 599 GmailMessage.prototype.getThread = function() { |
| 600 return null; |
| 601 }; |
| 602 |
| 603 /** |
| 604 * GMail search completion object that holds the DOM node of the |
| 605 * search completion. |
| 606 * |
| 607 * public: |
| 608 * @constructor |
| 609 * @param {Element} node The node that contains the GmailSearchCompletion. |
| 610 */ |
| 611 var GmailSearchCompletion = function(node) { |
| 612 this.node_ = node; |
| 613 }; |
| 614 |
| 615 /** |
| 616 * Returns the DOM node for the search completion. |
| 617 * |
| 618 * public: |
| 619 * @return {Object} The current search completion node. |
| 620 */ |
| 621 GmailSearchCompletion.prototype.getNode = function() { |
| 622 return this.node_; |
| 623 }; |
| 624 |
| 625 window.setTimeout(GmailApi.init_, 100); |
| OLD | NEW |