Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(11)

Side by Side Diff: chrome/browser/resources/chromeos/chromevox/cvox2/background/command_handler.js

Issue 2939273002: DO NOT SUBMIT: what chrome/browser/resources/ could eventually look like with clang-format (Closed)
Patch Set: Created 3 years, 6 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
1 // Copyright 2016 The Chromium Authors. All rights reserved. 1 // Copyright 2016 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 /** 5 /**
6 * @fileoverview ChromeVox commands. 6 * @fileoverview ChromeVox commands.
7 */ 7 */
8 8
9 goog.provide('CommandHandler'); 9 goog.provide('CommandHandler');
10 10
(...skipping 27 matching lines...) Expand all
38 if (!focusedNode) 38 if (!focusedNode)
39 ChromeVoxState.instance.setCurrentRange(null); 39 ChromeVoxState.instance.setCurrentRange(null);
40 }); 40 });
41 41
42 // These commands don't require a current range and work in all modes. 42 // These commands don't require a current range and work in all modes.
43 switch (command) { 43 switch (command) {
44 case 'speakTimeAndDate': 44 case 'speakTimeAndDate':
45 chrome.automation.getDesktop(function(d) { 45 chrome.automation.getDesktop(function(d) {
46 // First, try speaking the on-screen time. 46 // First, try speaking the on-screen time.
47 var allTime = d.findAll({role: RoleType.TIME}); 47 var allTime = d.findAll({role: RoleType.TIME});
48 allTime.filter(function(t) { return t.root.role == RoleType.DESKTOP; }); 48 allTime.filter(function(t) {
49 return t.root.role == RoleType.DESKTOP;
50 });
49 51
50 var timeString = ''; 52 var timeString = '';
51 allTime.forEach(function(t) { 53 allTime.forEach(function(t) {
52 if (t.name) timeString = t.name; 54 if (t.name)
55 timeString = t.name;
53 }); 56 });
54 if (timeString) { 57 if (timeString) {
55 cvox.ChromeVox.tts.speak(timeString, cvox.QueueMode.FLUSH); 58 cvox.ChromeVox.tts.speak(timeString, cvox.QueueMode.FLUSH);
56 } else { 59 } else {
57 // Fallback to the old way of speaking time. 60 // Fallback to the old way of speaking time.
58 var output = new Output(); 61 var output = new Output();
59 var dateTime = new Date(); 62 var dateTime = new Date();
60 output 63 output
61 .withString( 64 .withString(
62 dateTime.toLocaleTimeString() + ', ' + 65 dateTime.toLocaleTimeString() + ', ' +
(...skipping 397 matching lines...) Expand 10 before | Expand all | Expand 10 after
460 pred = AutomationPredicate.group; 463 pred = AutomationPredicate.group;
461 break; 464 break;
462 case 'nextGroup': 465 case 'nextGroup':
463 skipSync = true; 466 skipSync = true;
464 dir = Dir.FORWARD; 467 dir = Dir.FORWARD;
465 pred = AutomationPredicate.group; 468 pred = AutomationPredicate.group;
466 break; 469 break;
467 case 'jumpToTop': 470 case 'jumpToTop':
468 var node = AutomationUtil.findNodePost( 471 var node = AutomationUtil.findNodePost(
469 current.start.node.root, Dir.FORWARD, AutomationPredicate.leaf); 472 current.start.node.root, Dir.FORWARD, AutomationPredicate.leaf);
470 if (node) 473 if (node)
471 current = cursors.Range.fromNode(node); 474 current = cursors.Range.fromNode(node);
472 break; 475 break;
473 case 'jumpToBottom': 476 case 'jumpToBottom':
474 var node = AutomationUtil.findNodePost( 477 var node = AutomationUtil.findNodePost(
475 current.start.node.root, Dir.BACKWARD, AutomationPredicate.leaf); 478 current.start.node.root, Dir.BACKWARD, AutomationPredicate.leaf);
476 if (node) 479 if (node)
477 current = cursors.Range.fromNode(node); 480 current = cursors.Range.fromNode(node);
478 break; 481 break;
479 case 'forceClickOnCurrentItem': 482 case 'forceClickOnCurrentItem':
480 if (ChromeVoxState.instance.currentRange) { 483 if (ChromeVoxState.instance.currentRange) {
481 var actionNode = ChromeVoxState.instance.currentRange.start.node; 484 var actionNode = ChromeVoxState.instance.currentRange.start.node;
482 while (actionNode.role == RoleType.INLINE_TEXT_BOX || 485 while (actionNode.role == RoleType.INLINE_TEXT_BOX ||
483 actionNode.role == RoleType.STATIC_TEXT) 486 actionNode.role == RoleType.STATIC_TEXT)
484 actionNode = actionNode.parent; 487 actionNode = actionNode.parent;
485 if (actionNode.inPageLinkTarget) { 488 if (actionNode.inPageLinkTarget) {
486 ChromeVoxState.instance.navigateToRange( 489 ChromeVoxState.instance.navigateToRange(
487 cursors.Range.fromNode(actionNode.inPageLinkTarget)); 490 cursors.Range.fromNode(actionNode.inPageLinkTarget));
488 } else { 491 } else {
489 actionNode.doDefault(); 492 actionNode.doDefault();
490 } 493 }
491 } 494 }
492 // Skip all other processing; if focus changes, we should get an event 495 // Skip all other processing; if focus changes, we should get an event
493 // for that. 496 // for that.
494 return false; 497 return false;
495 case 'readFromHere': 498 case 'readFromHere':
496 ChromeVoxState.isReadingContinuously = true; 499 ChromeVoxState.isReadingContinuously = true;
497 var continueReading = function() { 500 var continueReading = function() {
498 if (!ChromeVoxState.isReadingContinuously || 501 if (!ChromeVoxState.isReadingContinuously ||
499 !ChromeVoxState.instance.currentRange_) 502 !ChromeVoxState.instance.currentRange_)
500 return; 503 return;
501 504
502 var prevRange = ChromeVoxState.instance.currentRange_; 505 var prevRange = ChromeVoxState.instance.currentRange_;
503 var newRange = 506 var newRange = ChromeVoxState.instance.currentRange_.move(
504 ChromeVoxState.instance.currentRange_.move( 507 cursors.Unit.NODE, Dir.FORWARD);
505 cursors.Unit.NODE, Dir.FORWARD);
506 508
507 // Stop if we've wrapped back to the document. 509 // Stop if we've wrapped back to the document.
508 var maybeDoc = newRange.start.node; 510 var maybeDoc = newRange.start.node;
509 if (maybeDoc.role == RoleType.ROOT_WEB_AREA && 511 if (maybeDoc.role == RoleType.ROOT_WEB_AREA &&
510 maybeDoc.parent.root.role == RoleType.DESKTOP) { 512 maybeDoc.parent.root.role == RoleType.DESKTOP) {
511 ChromeVoxState.isReadingContinuously = false; 513 ChromeVoxState.isReadingContinuously = false;
512 return; 514 return;
513 } 515 }
514 516
515 ChromeVoxState.instance.setCurrentRange(newRange); 517 ChromeVoxState.instance.setCurrentRange(newRange);
516 518
517 new Output() 519 new Output()
518 .withRichSpeechAndBraille(ChromeVoxState.instance.currentRange_, 520 .withRichSpeechAndBraille(
519 prevRange, 521 ChromeVoxState.instance.currentRange_, prevRange,
520 Output.EventType.NAVIGATE) 522 Output.EventType.NAVIGATE)
521 .onSpeechEnd(continueReading) 523 .onSpeechEnd(continueReading)
522 .go(); 524 .go();
523 }.bind(this); 525 }.bind(this);
524 526
525 new Output() 527 new Output()
526 .withRichSpeechAndBraille(ChromeVoxState.instance.currentRange_, 528 .withRichSpeechAndBraille(
527 null, 529 ChromeVoxState.instance.currentRange_, null,
528 Output.EventType.NAVIGATE) 530 Output.EventType.NAVIGATE)
529 .onSpeechEnd(continueReading) 531 .onSpeechEnd(continueReading)
530 .go(); 532 .go();
531 533
532 return false; 534 return false;
533 case 'contextMenu': 535 case 'contextMenu':
534 if (ChromeVoxState.instance.currentRange_) { 536 if (ChromeVoxState.instance.currentRange_) {
535 var actionNode = ChromeVoxState.instance.currentRange_.start.node; 537 var actionNode = ChromeVoxState.instance.currentRange_.start.node;
536 if (actionNode.role == RoleType.INLINE_TEXT_BOX) 538 if (actionNode.role == RoleType.INLINE_TEXT_BOX)
537 actionNode = actionNode.parent; 539 actionNode = actionNode.parent;
538 actionNode.showContextMenu(); 540 actionNode.showContextMenu();
(...skipping 24 matching lines...) Expand all
563 case 'readCurrentTitle': 565 case 'readCurrentTitle':
564 var target = ChromeVoxState.instance.currentRange_.start.node; 566 var target = ChromeVoxState.instance.currentRange_.start.node;
565 var output = new Output(); 567 var output = new Output();
566 568
567 if (target.root.role == RoleType.ROOT_WEB_AREA) { 569 if (target.root.role == RoleType.ROOT_WEB_AREA) {
568 // Web. 570 // Web.
569 target = target.root; 571 target = target.root;
570 output.withString(target.name || target.docUrl); 572 output.withString(target.name || target.docUrl);
571 } else { 573 } else {
572 // Views. 574 // Views.
573 while (target.role != RoleType.WINDOW) target = target.parent; 575 while (target.role != RoleType.WINDOW)
576 target = target.parent;
574 if (target) 577 if (target)
575 output.withString(target.name || ''); 578 output.withString(target.name || '');
576 } 579 }
577 output.go(); 580 output.go();
578 return false; 581 return false;
579 case 'readCurrentURL': 582 case 'readCurrentURL':
580 var output = new Output(); 583 var output = new Output();
581 var target = ChromeVoxState.instance.currentRange_.start.node.root; 584 var target = ChromeVoxState.instance.currentRange_.start.node.root;
582 output.withString(target.docUrl || '').go(); 585 output.withString(target.docUrl || '').go();
583 return false; 586 return false;
(...skipping 70 matching lines...) Expand 10 before | Expand all | Expand 10 after
654 predErrorMsg = 'no_cell_right'; 657 predErrorMsg = 'no_cell_right';
655 rootPred = AutomationPredicate.row; 658 rootPred = AutomationPredicate.row;
656 break; 659 break;
657 case 'goToRowFirstCell': 660 case 'goToRowFirstCell':
658 case 'goToRowLastCell': 661 case 'goToRowLastCell':
659 var node = current.start.node; 662 var node = current.start.node;
660 while (node && node.role != RoleType.ROW) 663 while (node && node.role != RoleType.ROW)
661 node = node.parent; 664 node = node.parent;
662 if (!node) 665 if (!node)
663 break; 666 break;
664 var end = AutomationUtil.findNodePost(node, 667 var end = AutomationUtil.findNodePost(
665 command == 'goToRowLastCell' ? Dir.BACKWARD : Dir.FORWARD, 668 node, command == 'goToRowLastCell' ? Dir.BACKWARD : Dir.FORWARD,
666 AutomationPredicate.leaf); 669 AutomationPredicate.leaf);
667 if (end) 670 if (end)
668 current = cursors.Range.fromNode(end); 671 current = cursors.Range.fromNode(end);
669 break; 672 break;
670 case 'goToColFirstCell': 673 case 'goToColFirstCell':
671 dir = Dir.FORWARD; 674 dir = Dir.FORWARD;
672 var node = current.start.node; 675 var node = current.start.node;
673 while (node && node.role != RoleType.TABLE) 676 while (node && node.role != RoleType.TABLE)
674 node = node.parent; 677 node = node.parent;
675 if (!node || !node.firstChild) 678 if (!node || !node.firstChild)
(...skipping 21 matching lines...) Expand all
697 predErrorMsg = 'no_cell_below'; 700 predErrorMsg = 'no_cell_below';
698 rootPred = AutomationPredicate.table; 701 rootPred = AutomationPredicate.table;
699 break; 702 break;
700 case 'goToFirstCell': 703 case 'goToFirstCell':
701 case 'goToLastCell': 704 case 'goToLastCell':
702 node = current.start.node; 705 node = current.start.node;
703 while (node && node.role != RoleType.TABLE) 706 while (node && node.role != RoleType.TABLE)
704 node = node.parent; 707 node = node.parent;
705 if (!node) 708 if (!node)
706 break; 709 break;
707 var end = AutomationUtil.findNodePost(node, 710 var end = AutomationUtil.findNodePost(
708 command == 'goToLastCell' ? Dir.BACKWARD : Dir.FORWARD, 711 node, command == 'goToLastCell' ? Dir.BACKWARD : Dir.FORWARD,
709 AutomationPredicate.leaf); 712 AutomationPredicate.leaf);
710 if (end) 713 if (end)
711 current = cursors.Range.fromNode(end); 714 current = cursors.Range.fromNode(end);
712 break; 715 break;
713 default: 716 default:
714 return true; 717 return true;
715 } 718 }
716 719
717 if (didNavigate) 720 if (didNavigate)
718 chrome.metricsPrivate.recordUserAction('Accessibility.ChromeVox.Navigate'); 721 chrome.metricsPrivate.recordUserAction('Accessibility.ChromeVox.Navigate');
(...skipping 14 matching lines...) Expand all
733 736
734 if (node) { 737 if (node) {
735 current = cursors.Range.fromNode(node); 738 current = cursors.Range.fromNode(node);
736 } else { 739 } else {
737 cvox.ChromeVox.earcons.playEarcon(cvox.Earcon.WRAP); 740 cvox.ChromeVox.earcons.playEarcon(cvox.Earcon.WRAP);
738 var root = AutomationUtil.getTopLevelRoot(bound) || bound.root; 741 var root = AutomationUtil.getTopLevelRoot(bound) || bound.root;
739 if (dir == Dir.FORWARD) { 742 if (dir == Dir.FORWARD) {
740 bound = root; 743 bound = root;
741 } else { 744 } else {
742 bound = AutomationUtil.findNodePost( 745 bound = AutomationUtil.findNodePost(
743 root, dir, AutomationPredicate.leaf) || bound; 746 root, dir, AutomationPredicate.leaf) ||
747 bound;
744 } 748 }
745 node = AutomationUtil.findNextNode( 749 node = AutomationUtil.findNextNode(
746 bound, dir, pred, {skipInitialAncestry: true}); 750 bound, dir, pred, {skipInitialAncestry: true});
747 751
748 if (node && !skipSync) { 752 if (node && !skipSync) {
749 node = AutomationUtil.findNodePre( 753 node = AutomationUtil.findNodePre(
750 node, Dir.FORWARD, AutomationPredicate.object) || node; 754 node, Dir.FORWARD, AutomationPredicate.object) ||
751 } 755 node;
756 }
752 757
753 if (node) { 758 if (node) {
754 current = cursors.Range.fromNode(node); 759 current = cursors.Range.fromNode(node);
755 } else if (predErrorMsg) { 760 } else if (predErrorMsg) {
756 cvox.ChromeVox.tts.speak( 761 cvox.ChromeVox.tts.speak(
757 Msgs.getMsg(predErrorMsg), cvox.QueueMode.FLUSH); 762 Msgs.getMsg(predErrorMsg), cvox.QueueMode.FLUSH);
758 return false; 763 return false;
759 } 764 }
760 } 765 }
761 } 766 }
762 } 767 }
763 768
764 if (current) 769 if (current)
765 ChromeVoxState.instance.navigateToRange(current, undefined, speechProps); 770 ChromeVoxState.instance.navigateToRange(current, undefined, speechProps);
766 771
767 return false; 772 return false;
768 }; 773 };
(...skipping 16 matching lines...) Expand all
785 chrome.commands.onCommand.addListener(CommandHandler.onCommand); 790 chrome.commands.onCommand.addListener(CommandHandler.onCommand);
786 }; 791 };
787 792
788 /** 793 /**
789 * Increase or decrease a speech property and make an announcement. 794 * Increase or decrease a speech property and make an announcement.
790 * @param {string} propertyName The name of the property to change. 795 * @param {string} propertyName The name of the property to change.
791 * @param {boolean} increase If true, increases the property value by one 796 * @param {boolean} increase If true, increases the property value by one
792 * step size, otherwise decreases. 797 * step size, otherwise decreases.
793 * @private 798 * @private
794 */ 799 */
795 CommandHandler.increaseOrDecreaseSpeechProperty_ = 800 CommandHandler.increaseOrDecreaseSpeechProperty_ = function(
796 function(propertyName, increase) { 801 propertyName, increase) {
797 cvox.ChromeVox.tts.increaseOrDecreaseProperty(propertyName, increase); 802 cvox.ChromeVox.tts.increaseOrDecreaseProperty(propertyName, increase);
798 var announcement; 803 var announcement;
799 var valueAsPercent = Math.round( 804 var valueAsPercent =
800 cvox.ChromeVox.tts.propertyToPercentage(propertyName) * 100); 805 Math.round(cvox.ChromeVox.tts.propertyToPercentage(propertyName) * 100);
801 switch (propertyName) { 806 switch (propertyName) {
802 case cvox.AbstractTts.RATE: 807 case cvox.AbstractTts.RATE:
803 announcement = Msgs.getMsg('announce_rate', [valueAsPercent]); 808 announcement = Msgs.getMsg('announce_rate', [valueAsPercent]);
804 break; 809 break;
805 case cvox.AbstractTts.PITCH: 810 case cvox.AbstractTts.PITCH:
806 announcement = Msgs.getMsg('announce_pitch', [valueAsPercent]); 811 announcement = Msgs.getMsg('announce_pitch', [valueAsPercent]);
807 break; 812 break;
808 case cvox.AbstractTts.VOLUME: 813 case cvox.AbstractTts.VOLUME:
809 announcement = Msgs.getMsg('announce_volume', [valueAsPercent]); 814 announcement = Msgs.getMsg('announce_volume', [valueAsPercent]);
810 break; 815 break;
(...skipping 15 matching lines...) Expand all
826 * Called when an image frame is received on a node. 831 * Called when an image frame is received on a node.
827 * @param {!(AutomationEvent|CustomAutomationEvent)} event The event. 832 * @param {!(AutomationEvent|CustomAutomationEvent)} event The event.
828 * @private 833 * @private
829 */ 834 */
830 CommandHandler.onImageFrameUpdated_ = function(event) { 835 CommandHandler.onImageFrameUpdated_ = function(event) {
831 var target = event.target; 836 var target = event.target;
832 if (target != CommandHandler.imageNode_) 837 if (target != CommandHandler.imageNode_)
833 return; 838 return;
834 839
835 if (!AutomationUtil.isDescendantOf( 840 if (!AutomationUtil.isDescendantOf(
836 ChromeVoxState.instance.currentRange.start.node, 841 ChromeVoxState.instance.currentRange.start.node,
837 CommandHandler.imageNode_)) { 842 CommandHandler.imageNode_)) {
838 CommandHandler.imageNode_.removeEventListener( 843 CommandHandler.imageNode_.removeEventListener(
839 EventType.IMAGE_FRAME_UPDATED, 844 EventType.IMAGE_FRAME_UPDATED, CommandHandler.onImageFrameUpdated_,
840 CommandHandler.onImageFrameUpdated_, false); 845 false);
841 CommandHandler.imageNode_ = null; 846 CommandHandler.imageNode_ = null;
842 return; 847 return;
843 } 848 }
844 849
845 if (target.imageDataUrl) { 850 if (target.imageDataUrl) {
846 cvox.ChromeVox.braille.writeRawImage(target.imageDataUrl); 851 cvox.ChromeVox.braille.writeRawImage(target.imageDataUrl);
847 cvox.ChromeVox.braille.freeze(); 852 cvox.ChromeVox.braille.freeze();
848 } 853 }
849 }; 854 };
850 855
851 /** 856 /**
852 * Handle the command to view the first graphic within the current range 857 * Handle the command to view the first graphic within the current range
853 * as braille. 858 * as braille.
854 * @param {!AutomationNode} current The current range. 859 * @param {!AutomationNode} current The current range.
855 * @private 860 * @private
856 */ 861 */
857 CommandHandler.viewGraphicAsBraille_ = function(current) { 862 CommandHandler.viewGraphicAsBraille_ = function(current) {
858 if (CommandHandler.imageNode_) { 863 if (CommandHandler.imageNode_) {
859 CommandHandler.imageNode_.removeEventListener( 864 CommandHandler.imageNode_.removeEventListener(
860 EventType.IMAGE_FRAME_UPDATED, 865 EventType.IMAGE_FRAME_UPDATED, CommandHandler.onImageFrameUpdated_,
861 CommandHandler.onImageFrameUpdated_, false); 866 false);
862 CommandHandler.imageNode_ = null; 867 CommandHandler.imageNode_ = null;
863 } 868 }
864 869
865 // Find the first node within the current range that supports image data. 870 // Find the first node within the current range that supports image data.
866 var imageNode = AutomationUtil.findNodePost( 871 var imageNode = AutomationUtil.findNodePost(
867 current.start.node, Dir.FORWARD, 872 current.start.node, Dir.FORWARD, AutomationPredicate.supportsImageData);
868 AutomationPredicate.supportsImageData);
869 if (!imageNode) 873 if (!imageNode)
870 return; 874 return;
871 875
872 imageNode.addEventListener(EventType.IMAGE_FRAME_UPDATED, 876 imageNode.addEventListener(
873 this.onImageFrameUpdated_, false); 877 EventType.IMAGE_FRAME_UPDATED, this.onImageFrameUpdated_, false);
874 CommandHandler.imageNode_ = imageNode; 878 CommandHandler.imageNode_ = imageNode;
875 if (imageNode.imageDataUrl) { 879 if (imageNode.imageDataUrl) {
876 var event = new CustomAutomationEvent( 880 var event = new CustomAutomationEvent(
877 EventType.IMAGE_FRAME_UPDATED, imageNode, 'page'); 881 EventType.IMAGE_FRAME_UPDATED, imageNode, 'page');
878 CommandHandler.onImageFrameUpdated_(event); 882 CommandHandler.onImageFrameUpdated_(event);
879 } else { 883 } else {
880 imageNode.getImageData(0, 0); 884 imageNode.getImageData(0, 0);
881 } 885 }
882 }; 886 };
883 887
884 /** 888 /**
885 * Performs global initialization. 889 * Performs global initialization.
886 * @private 890 * @private
887 */ 891 */
888 CommandHandler.init_ = function() { 892 CommandHandler.init_ = function() {
889 var firstRunId = 'jdgcneonijmofocbhmijhacgchbihela'; 893 var firstRunId = 'jdgcneonijmofocbhmijhacgchbihela';
890 chrome.runtime.onMessageExternal.addListener( 894 chrome.runtime.onMessageExternal.addListener(function(
891 function(request, sender, sendResponse) { 895 request, sender, sendResponse) {
892 if (sender.id != firstRunId) 896 if (sender.id != firstRunId)
893 return; 897 return;
894 898
895 if (request.openTutorial) { 899 if (request.openTutorial) {
896 var launchTutorial = function(desktop, evt) { 900 var launchTutorial = function(desktop, evt) {
897 desktop.removeEventListener( 901 desktop.removeEventListener(
898 chrome.automation.EventType.FOCUS, launchTutorial, true); 902 chrome.automation.EventType.FOCUS, launchTutorial, true);
899 CommandHandler.onCommand('help'); 903 CommandHandler.onCommand('help');
900 }; 904 };
901 905
902 // Since we get this command early on ChromeVox launch, the first run 906 // Since we get this command early on ChromeVox launch, the first run
903 // UI is not yet shown. Monitor for when first run gets focused, and 907 // UI is not yet shown. Monitor for when first run gets focused, and
904 // show our tutorial. 908 // show our tutorial.
905 chrome.automation.getDesktop(function(desktop) { 909 chrome.automation.getDesktop(function(desktop) {
906 launchTutorial = launchTutorial.bind(this, desktop); 910 launchTutorial = launchTutorial.bind(this, desktop);
907 desktop.addEventListener( 911 desktop.addEventListener(
908 chrome.automation.EventType.FOCUS, launchTutorial, true); 912 chrome.automation.EventType.FOCUS, launchTutorial, true);
909 });
910 }
911 }); 913 });
914 }
915 });
912 }; 916 };
913 917
914 CommandHandler.init_(); 918 CommandHandler.init_();
915 919
916 }); // goog.scope 920 }); // goog.scope
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698