Home | History | Annotate | Download | only in sync_internals
      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