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
5cr.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
189document.addEventListener(
190    'DOMContentLoaded', chrome.sync.about_tab.onLoad, false);
191