chromium/components/sync/service/resources/traffic_log.ts

// Copyright 2017 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'chrome://resources/js/jstemplate_compiled.js';

import {assert} from 'chrome://resources/js/assert.js';
import {addWebUiListener} from 'chrome://resources/js/cr.js';

let instance: TrafficLogTag|null = null;

export interface ProtocolEvent {
  time: string;
}

class TrafficLogTag {
  container: HTMLElement|null = null;
  protocolEvents: ProtocolEvent[] = [];
  knownEventTimestamps: Set<string> = new Set();

  /**
   * Helper to determine if the window is scrolled to its bottom limit.
   * @return true if the container is scrolled to the bottom
   */
  private isScrolledToBottom_(): boolean {
    return (window.innerHeight + window.scrollY) >= document.body.offsetHeight;
  }

  /**
   * Helper to scroll the window to its bottom.
   */
  private scrollToBottom_() {
    window.scrollTo(0, document.body.scrollHeight);
  }

  /**
   * Callback for incoming protocol events.
   * @param details The protocol event.
   */
  private onReceivedProtocolEvent_(details: ProtocolEvent) {
    if (this.knownEventTimestamps.has(details.time)) {
      return;
    }

    this.knownEventTimestamps.add(details.time);
    this.protocolEvents.push(details);

    const shouldScrollDown = this.isScrolledToBottom_();

    assert(this.container);
    jstProcess(
        new JsEvalContext({events: this.protocolEvents}), this.container);

    if (shouldScrollDown) {
      this.scrollToBottom_();
    }
  }

  /**
   * Toggles the given traffic event entry div's "expanded" state.
   * @param e the click event that triggered the toggle.
   */
  private expandListener_(e: Event) {
    if ((e.target as HTMLElement).classList.contains('proto')) {
      // We ignore proto clicks to keep it copyable.
      return;
    }
    let trafficEventDiv = e.target as HTMLElement;
    // Click might be on div's child.
    if (trafficEventDiv.nodeName !== 'DIV' && trafficEventDiv.parentNode) {
      trafficEventDiv = trafficEventDiv.parentNode as HTMLElement;
    }
    trafficEventDiv.classList.toggle('traffic-event-entry-expanded-fullscreen');
  }

  /**
   * Attaches a listener to the given traffic event entry div.
   */
  addExpandListener(element: HTMLElement) {
    element.addEventListener('click', this.expandListener_, false);
  }

  onLoad() {
    const container = document.querySelector<HTMLElement>(
        '#traffic-event-fullscreen-container');
    assert(container);
    this.container = container;

    addWebUiListener(
        'onProtocolEvent', this.onReceivedProtocolEvent_.bind(this));

    // Make the prototype jscontent element disappear.
    jstProcess(new JsEvalContext({}), this.container);
  }

  static getInstance(): TrafficLogTag {
    return instance || (instance = new TrafficLogTag());
  }
}

// For JS eval.
Object.assign(window, {TrafficLogTag});

document.addEventListener('DOMContentLoaded', () => {
  TrafficLogTag.getInstance().onLoad();
});