Javascript  |  191行  |  5.74 KB

// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

cr.define('chrome.sync.about_tab', function() {
  // Contains the latest snapshot of sync about info.
  chrome.sync.aboutInfo = {};

  function highlightIfChanged(node, oldVal, newVal) {
    function clearHighlight() {
      this.removeAttribute('highlighted');
    }

    var oldStr = oldVal.toString();
    var newStr = newVal.toString();
    if (oldStr != '' && oldStr != newStr) {
      // Note the addListener function does not end up creating duplicate
      // listeners.  There can be only one listener per event at a time.
      // Reference: https://developer.mozilla.org/en/DOM/element.addEventListener
      node.addEventListener('webkitAnimationEnd', clearHighlight, false);
      node.setAttribute('highlighted', '');
    }
  }

  function refreshAboutInfo(aboutInfo) {
    chrome.sync.aboutInfo = aboutInfo;
    var aboutInfoDiv = $('about-info');
    jstProcess(new JsEvalContext(aboutInfo), aboutInfoDiv);
  }

  function onAboutInfoUpdatedEvent(e) {
    refreshAboutInfo(e.details);
  }

  /**
   * Helper to determine if an element is scrolled to its bottom limit.
   * @param {Element} elem element to check
   * @return {boolean} true if the element is scrolled to the bottom
   */
  function isScrolledToBottom(elem) {
    return elem.scrollHeight - elem.scrollTop == elem.clientHeight;
  }

  /**
   * Helper to scroll an element to its bottom limit.
   * @param {Element} elem element to be scrolled
   */
  function scrollToBottom(elem) {
    elem.scrollTop = elem.scrollHeight - elem.clientHeight;
  }

  /** Container for accumulated sync protocol events. */
  var protocolEvents = [];

  /** We may receive re-delivered events.  Keep a record of ones we've seen. */
  var knownEventTimestamps = {};

  /**
   * Callback for incoming protocol events.
   * @param {Event} e The protocol event.
   */
  function onReceivedProtocolEvent(e) {
    var details = e.details;

    // Return early if we've seen this event before.  Assumes that timestamps
    // are sufficiently high resolution to uniquely identify an event.
    if (knownEventTimestamps.hasOwnProperty(details.time)) {
      return;
    }

    knownEventTimestamps[details.time] = true;
    protocolEvents.push(details);

    var trafficContainer = $('traffic-event-container');

    // Scroll to the bottom if we were already at the bottom.  Otherwise, leave
    // the scrollbar alone.
    var shouldScrollDown = isScrolledToBottom(trafficContainer);

    var context = new JsEvalContext({ events: protocolEvents });
    jstProcess(context, trafficContainer);

    if (shouldScrollDown)
      scrollToBottom(trafficContainer);
  }

  /**
   * Initializes state and callbacks for the protocol event log UI.
   */
  function initProtocolEventLog() {
    chrome.sync.events.addEventListener(
        'onProtocolEvent', onReceivedProtocolEvent);

    // Make the prototype jscontent element disappear.
    jstProcess({}, $('traffic-event-container'));
  }

  /**
   * Initializes listeners for status dump and import UI.
   */
  function initStatusDumpButton() {
    $('status-data').hidden = true;

    var dumpStatusButton = $('dump-status');
    dumpStatusButton.addEventListener('click', function(event) {
      var aboutInfo = chrome.sync.aboutInfo;
      if (!$('include-ids').checked) {
        aboutInfo.details = chrome.sync.aboutInfo.details.filter(function(el) {
          return !el.is_sensitive;
        });
      }
      var data = '';
      data += new Date().toString() + '\n';
      data += '======\n';
      data += 'Status\n';
      data += '======\n';
      data += JSON.stringify(aboutInfo, null, 2) + '\n';

      $('status-text').value = data;
      $('status-data').hidden = false;
    });

    var importStatusButton = $('import-status');
    importStatusButton.addEventListener('click', function(event) {
      $('status-data').hidden = false;
      if ($('status-text').value.length == 0) {
        $('status-text').value =
            'Paste sync status dump here then click import.';
        return;
      }

      // First remove any characters before the '{'.
      var data = $('status-text').value;
      var firstBrace = data.indexOf('{');
      if (firstBrace < 0) {
        $('status-text').value = 'Invalid sync status dump.';
        return;
      }
      data = data.substr(firstBrace);

      // Remove listeners to prevent sync events from overwriting imported data.
      chrome.sync.events.removeEventListener(
          'onAboutInfoUpdated',
          onAboutInfoUpdatedEvent);

      var aboutInfo = JSON.parse(data);
      refreshAboutInfo(aboutInfo);
    });
  }

  /**
   * Toggles the given traffic event entry div's "expanded" state.
   * @param {MouseEvent} e the click event that triggered the toggle.
   */
  function expandListener(e) {
    e.target.classList.toggle('traffic-event-entry-expanded');
  }

  /**
   * Attaches a listener to the given traffic event entry div.
   * @param {HTMLElement} element the element to attach the listener to.
   */
  function addExpandListener(element) {
    element.addEventListener('click', expandListener, false);
  }

  function onLoad() {
    initStatusDumpButton();
    initProtocolEventLog();

    chrome.sync.events.addEventListener(
        'onAboutInfoUpdated',
        onAboutInfoUpdatedEvent);

    // Register to receive a stream of event notifications.
    chrome.sync.registerForEvents();

    // Request an about info update event to initialize the page.
    chrome.sync.requestUpdatedAboutInfo();
  }

  return {
    onLoad: onLoad,
    addExpandListener: addExpandListener,
    highlightIfChanged: highlightIfChanged
  };
});

document.addEventListener(
    'DOMContentLoaded', chrome.sync.about_tab.onLoad, false);