1 // Copyright (c) 2012 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 cr.define('chrome.sync.about_tab', function() { 6 // Contains the latest snapshot of sync about info. 7 chrome.sync.aboutInfo = {}; 8 9 function highlightIfChanged(node, oldVal, newVal) { 10 function clearHighlight() { 11 this.removeAttribute('highlighted'); 12 } 13 14 var oldStr = oldVal.toString(); 15 var newStr = newVal.toString(); 16 if (oldStr != '' && oldStr != newStr) { 17 // Note the addListener function does not end up creating duplicate 18 // listeners. There can be only one listener per event at a time. 19 // Reference: https://developer.mozilla.org/en/DOM/element.addEventListener 20 node.addEventListener('webkitAnimationEnd', clearHighlight, false); 21 node.setAttribute('highlighted', ''); 22 } 23 } 24 25 function refreshAboutInfo(aboutInfo) { 26 chrome.sync.aboutInfo = aboutInfo; 27 var aboutInfoDiv = $('about-info'); 28 jstProcess(new JsEvalContext(aboutInfo), aboutInfoDiv); 29 } 30 31 function onAboutInfoUpdatedEvent(e) { 32 refreshAboutInfo(e.details); 33 } 34 35 /** 36 * Helper to determine if an element is scrolled to its bottom limit. 37 * @param {Element} elem element to check 38 * @return {boolean} true if the element is scrolled to the bottom 39 */ 40 function isScrolledToBottom(elem) { 41 return elem.scrollHeight - elem.scrollTop == elem.clientHeight; 42 } 43 44 /** 45 * Helper to scroll an element to its bottom limit. 46 * @param {Element} elem element to be scrolled 47 */ 48 function scrollToBottom(elem) { 49 elem.scrollTop = elem.scrollHeight - elem.clientHeight; 50 } 51 52 /** Container for accumulated sync protocol events. */ 53 var protocolEvents = []; 54 55 /** We may receive re-delivered events. Keep a record of ones we've seen. */ 56 var knownEventTimestamps = {}; 57 58 /** 59 * Callback for incoming protocol events. 60 * @param {Event} e The protocol event. 61 */ 62 function onReceivedProtocolEvent(e) { 63 var details = e.details; 64 65 // Return early if we've seen this event before. Assumes that timestamps 66 // are sufficiently high resolution to uniquely identify an event. 67 if (knownEventTimestamps.hasOwnProperty(details.time)) { 68 return; 69 } 70 71 knownEventTimestamps[details.time] = true; 72 protocolEvents.push(details); 73 74 var trafficContainer = $('traffic-event-container'); 75 76 // Scroll to the bottom if we were already at the bottom. Otherwise, leave 77 // the scrollbar alone. 78 var shouldScrollDown = isScrolledToBottom(trafficContainer); 79 80 var context = new JsEvalContext({ events: protocolEvents }); 81 jstProcess(context, trafficContainer); 82 83 if (shouldScrollDown) 84 scrollToBottom(trafficContainer); 85 } 86 87 /** 88 * Initializes state and callbacks for the protocol event log UI. 89 */ 90 function initProtocolEventLog() { 91 chrome.sync.events.addEventListener( 92 'onProtocolEvent', onReceivedProtocolEvent); 93 94 // Make the prototype jscontent element disappear. 95 jstProcess({}, $('traffic-event-container')); 96 } 97 98 /** 99 * Initializes listeners for status dump and import UI. 100 */ 101 function initStatusDumpButton() { 102 $('status-data').hidden = true; 103 104 var dumpStatusButton = $('dump-status'); 105 dumpStatusButton.addEventListener('click', function(event) { 106 var aboutInfo = chrome.sync.aboutInfo; 107 if (!$('include-ids').checked) { 108 aboutInfo.details = chrome.sync.aboutInfo.details.filter(function(el) { 109 return !el.is_sensitive; 110 }); 111 } 112 var data = ''; 113 data += new Date().toString() + '\n'; 114 data += '======\n'; 115 data += 'Status\n'; 116 data += '======\n'; 117 data += JSON.stringify(aboutInfo, null, 2) + '\n'; 118 119 $('status-text').value = data; 120 $('status-data').hidden = false; 121 }); 122 123 var importStatusButton = $('import-status'); 124 importStatusButton.addEventListener('click', function(event) { 125 $('status-data').hidden = false; 126 if ($('status-text').value.length == 0) { 127 $('status-text').value = 128 'Paste sync status dump here then click import.'; 129 return; 130 } 131 132 // First remove any characters before the '{'. 133 var data = $('status-text').value; 134 var firstBrace = data.indexOf('{'); 135 if (firstBrace < 0) { 136 $('status-text').value = 'Invalid sync status dump.'; 137 return; 138 } 139 data = data.substr(firstBrace); 140 141 // Remove listeners to prevent sync events from overwriting imported data. 142 chrome.sync.events.removeEventListener( 143 'onAboutInfoUpdated', 144 onAboutInfoUpdatedEvent); 145 146 var aboutInfo = JSON.parse(data); 147 refreshAboutInfo(aboutInfo); 148 }); 149 } 150 151 /** 152 * Toggles the given traffic event entry div's "expanded" state. 153 * @param {MouseEvent} e the click event that triggered the toggle. 154 */ 155 function expandListener(e) { 156 e.target.classList.toggle('traffic-event-entry-expanded'); 157 } 158 159 /** 160 * Attaches a listener to the given traffic event entry div. 161 * @param {HTMLElement} element the element to attach the listener to. 162 */ 163 function addExpandListener(element) { 164 element.addEventListener('click', expandListener, false); 165 } 166 167 function onLoad() { 168 initStatusDumpButton(); 169 initProtocolEventLog(); 170 171 chrome.sync.events.addEventListener( 172 'onAboutInfoUpdated', 173 onAboutInfoUpdatedEvent); 174 175 // Register to receive a stream of event notifications. 176 chrome.sync.registerForEvents(); 177 178 // Request an about info update event to initialize the page. 179 chrome.sync.requestUpdatedAboutInfo(); 180 } 181 182 return { 183 onLoad: onLoad, 184 addExpandListener: addExpandListener, 185 highlightIfChanged: highlightIfChanged 186 }; 187 }); 188 189 document.addEventListener( 190 'DOMContentLoaded', chrome.sync.about_tab.onLoad, false); 191