text: /**
   * @license
   * Copyright 2017 Google Inc. All Rights Reserved.
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   *      http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS-IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  'use strict';
  
  /**
   * @fileoverview This file contains helpers for constructing and rendering the
   * critical request chains network tree.
   */
  
  /* globals self Util */
  
  /** @typedef {import('./dom.js')} DOM */
  
  class CriticalRequestChainRenderer {
    /**
     * Create render context for critical-request-chain tree display.
     * @param {LH.Audit.SimpleCriticalRequestNode} tree
     * @return {{tree: LH.Audit.SimpleCriticalRequestNode, startTime: number, transferSize: number}}
     */
    static initTree(tree) {
      let startTime = 0;
      const rootNodes = Object.keys(tree);
      if (rootNodes.length > 0) {
        const node = tree[rootNodes[0]];
        startTime = node.request.startTime;
      }
  
      return {tree, startTime, transferSize: 0};
    }
  
    /**
     * Helper to create context for each critical-request-chain node based on its
     * parent. Calculates if this node is the last child, whether it has any
     * children itself and what the tree looks like all the way back up to the root,
     * so the tree markers can be drawn correctly.
     * @param {LH.Audit.SimpleCriticalRequestNode} parent
     * @param {string} id
     * @param {number} startTime
     * @param {number} transferSize
     * @param {Array<boolean>=} treeMarkers
     * @param {boolean=} parentIsLastChild
     * @return {CRCSegment}
     */
    static createSegment(parent, id, startTime, transferSize, treeMarkers, parentIsLastChild) {
      const node = parent[id];
      const siblings = Object.keys(parent);
      const isLastChild = siblings.indexOf(id) === (siblings.length - 1);
      const hasChildren = !!node.children && Object.keys(node.children).length > 0;
  
      // Copy the tree markers so that we don't change by reference.
      const newTreeMarkers = Array.isArray(treeMarkers) ? treeMarkers.slice(0) : [];
  
      // Add on the new entry.
      if (typeof parentIsLastChild !== 'undefined') {
        newTreeMarkers.push(!parentIsLastChild);
      }
  
      return {
        node,
        isLastChild,
        hasChildren,
        startTime,
        transferSize: transferSize + node.request.transferSize,
        treeMarkers: newTreeMarkers,
      };
    }
  
    /**
     * Creates the DOM for a tree segment.
     * @param {DOM} dom
     * @param {DocumentFragment} tmpl
     * @param {CRCSegment} segment
     * @param {DetailsRenderer} detailsRenderer
     * @return {Node}
     */
    static createChainNode(dom, tmpl, segment, detailsRenderer) {
      const chainsEl = dom.cloneTemplate('#tmpl-lh-crc__chains', tmpl);
  
      // Hovering over request shows full URL.
      dom.find('.crc-node', chainsEl).setAttribute('title', segment.node.request.url);
  
      const treeMarkeEl = dom.find('.crc-node__tree-marker', chainsEl);
  
      // Construct lines and add spacers for sub requests.
      segment.treeMarkers.forEach(separator => {
        if (separator) {
          treeMarkeEl.appendChild(dom.createElement('span', 'tree-marker vert'));
          treeMarkeEl.appendChild(dom.createElement('span', 'tree-marker'));
        } else {
          treeMarkeEl.appendChild(dom.createElement('span', 'tree-marker'));
          treeMarkeEl.appendChild(dom.createElement('span', 'tree-marker'));
        }
      });
  
      if (segment.isLastChild) {
        treeMarkeEl.appendChild(dom.createElement('span', 'tree-marker up-right'));
        treeMarkeEl.appendChild(dom.createElement('span', 'tree-marker right'));
      } else {
        treeMarkeEl.appendChild(dom.createElement('span', 'tree-marker vert-right'));
        treeMarkeEl.appendChild(dom.createElement('span', 'tree-marker right'));
      }
  
      if (segment.hasChildren) {
        treeMarkeEl.appendChild(dom.createElement('span', 'tree-marker horiz-down'));
      } else {
        treeMarkeEl.appendChild(dom.createElement('span', 'tree-marker right'));
      }
  
      // Fill in url, host, and request size information.
      const url = segment.node.request.url;
      const linkEl = detailsRenderer.renderTextURL(url);
      const treevalEl = dom.find('.crc-node__tree-value', chainsEl);
      treevalEl.appendChild(linkEl);
  
      if (!segment.hasChildren) {
        const {startTime, endTime, transferSize} = segment.node.request;
        const span = dom.createElement('span', 'crc-node__chain-duration');
        span.textContent = ' - ' + Util.i18n.formatMilliseconds((endTime - startTime) * 1000) + ', ';
        const span2 = dom.createElement('span', 'crc-node__chain-duration');
        span2.textContent = Util.i18n.formatBytesToKB(transferSize, 0.01);
  
        treevalEl.appendChild(span);
        treevalEl.appendChild(span2);
      }
  
      return chainsEl;
    }
  
    /**
     * Recursively builds a tree from segments.
     * @param {DOM} dom
     * @param {DocumentFragment} tmpl
     * @param {CRCSegment} segment
     * @param {Element} elem Parent element.
     * @param {LH.Audit.Details.CriticalRequestChain} details
     * @param {DetailsRenderer} detailsRenderer
     */
    static buildTree(dom, tmpl, segment, elem, details, detailsRenderer) {
      elem.appendChild(CRCRenderer.createChainNode(dom, tmpl, segment, detailsRenderer));
      if (segment.node.children) {
        for (const key of Object.keys(segment.node.children)) {
          const childSegment = CRCRenderer.createSegment(segment.node.children, key,
            segment.startTime, segment.transferSize, segment.treeMarkers, segment.isLastChild);
          CRCRenderer.buildTree(dom, tmpl, childSegment, elem, details, detailsRenderer);
        }
      }
    }
  
    /**
     * @param {DOM} dom
     * @param {ParentNode} templateContext
     * @param {LH.Audit.Details.CriticalRequestChain} details
     * @param {DetailsRenderer} detailsRenderer
     * @return {Element}
     */
    static render(dom, templateContext, details, detailsRenderer) {
      const tmpl = dom.cloneTemplate('#tmpl-lh-crc', templateContext);
      const containerEl = dom.find('.lh-crc', tmpl);
  
      // Fill in top summary.
      dom.find('.crc-initial-nav', tmpl).textContent = Util.i18n.strings.crcInitialNavigation;
      dom.find('.lh-crc__longest_duration_label', tmpl).textContent =
          Util.i18n.strings.crcLongestDurationLabel;
      dom.find('.lh-crc__longest_duration', tmpl).textContent =
          Util.i18n.formatMilliseconds(details.longestChain.duration);
  
      // Construct visual tree.
      const root = CRCRenderer.initTree(details.chains);
      for (const key of Object.keys(root.tree)) {
        const segment = CRCRenderer.createSegment(root.tree, key, root.startTime, root.transferSize);
        CRCRenderer.buildTree(dom, tmpl, segment, containerEl, details, detailsRenderer);
      }
  
      return dom.find('.lh-crc-container', tmpl);
    }
  }
  
  // Alias b/c the name is really long.
  const CRCRenderer = CriticalRequestChainRenderer;
  
  // Allow Node require()'ing.
  if (typeof module !== 'undefined' && module.exports) {
    module.exports = CriticalRequestChainRenderer;
  } else {
    self.CriticalRequestChainRenderer = CriticalRequestChainRenderer;
  }
  
  /** @typedef {{
        node: LH.Audit.SimpleCriticalRequestNode[string],
        isLastChild: boolean,
        hasChildren: boolean,
        startTime: number,
        transferSize: number,
        treeMarkers: Array<boolean>
    }} CRCSegment
   */
  