chromium/tools/binary_size/libsupersize/viewer/static/index.js

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

'use strict';

/** @type {string}  */
const DO_NOT_DIFF = 'Don\'t diff';

/** @type {string} Domain hosting the viewer.html */
const FIREBASE_HOST = 'https://chrome-supersize.firebaseapp.com'

/** @type {string} Storage bucket hosting the size diffs. */
const SIZE_FILEHOST = 'https://storage.googleapis.com/chrome-supersize'

/**
 * @param {Array<*>} options
 * @return {DocumentFragment}
 */
function buildOptions(options) {
  const fragment = document.createDocumentFragment();
  for (let option of options) {
    const optionEl = document.createElement('option');
    optionEl.value = option;
    optionEl.textContent = option;
    fragment.appendChild(optionEl);
  }
  return fragment;
}

/**
 * @param {NodeListOf<HTMLOptionElement>} optList
 * @param {number} index
 */
function selectOption(optList, index) {
  const n = optList.length;
  if (n > 0)
    optList[((index % n) + n) % n].selected = true;
}

/**
 * @param {HTMLFormElement} form
 * @param {function(): string} fetchDataUrl
 */
function setSubmitListener(form, fetchDataUrl) {
  form.addEventListener('submit', event => {
    event.preventDefault();
    const dataUrl = fetchDataUrl();
    window.open(`${FIREBASE_HOST}/viewer.html?load_url=${dataUrl}`);
  });
}

// Milestones.
(async () => {
  // Milestones.
  const milestoneResponse = await fetch(
      `${SIZE_FILEHOST}/milestones/milestones.json`);
  const milestonesPushed = (await milestoneResponse.json())['pushed'];

  // Official Builds
  const officialBuildsResponse =
      await fetch(`${SIZE_FILEHOST}/official_builds/canary_reports.json`);
  const officialBuildsPushed = (await officialBuildsResponse.json())['pushed'];

  if (document.readyState === 'loading') {
    await new Promise(resolve => {
      document.onreadystatechange =
          () => {
            if (document.readyState !== 'loading') {
              resolve();
              document.onreadystatechange = null;
            }
          }
    });
  }

  /** @type {HTMLButtonElement} */
  const submitButton =   /** @type {HTMLButtonElement} */ (
      document.getElementById('submit-button'));

  /** @type {HTMLFormElement} */
  const form = /** @type {HTMLFormElement} */ (
      document.getElementById('select-form'));

  /** @type {HTMLSelectElement} */
  const selApk = /** @type {HTMLSelectElement} */ (
      form.elements.namedItem('apk'));

  /** @type {HTMLSelectElement} */
  const selVersion1 = /** @type {HTMLSelectElement} */ (
      form.elements.namedItem('version1'));

  /** @type {HTMLSelectElement} */
  const selVersion2 = /** @type {HTMLSelectElement} */ (
      form.elements.namedItem('version2'));

  /** @type {HTMLInputElement} */
  const showAll = /** @type {HTMLInputElement} */ (
      document.getElementsByName('showall')[0]);

  /** @type {HTMLButtonElement} */
  const btnOpen = /** @type {HTMLButtonElement} */ (
      form.querySelector('button[type="submit"]'));

  /** @type {Array<string>} */
  let activeVersions = [];

  /**
   * @param {string} cpu
   * @param {string} apk
   * @return {string}
   */
  function fmtCpuApk(cpu, apk) {
    return cpu + '/' + apk;
  }

  /**
   * @param {Array<string>} cpus
   * @param {Array<string>} apks
   * @return {Array<string>}
   */
  function cpuApkPairs(cpus, apks) {
    let out = [];
    for (let cpu of cpus) {
      for (let apk of apks) {
        // Chrome.apk not available for arm_64
        if (!(cpu === 'arm_64' && apk === 'Chrome.apk')) {
          out.push(fmtCpuApk(cpu, apk));
        }
      }
    }
    return out;
  }

  function updateApk() {
    // Overwrites the apk selector with entries of format {cpu}/{apk}
    let mainApks = cpuApkPairs(milestonesPushed.cpu, milestonesPushed.apk);
    let canaryApks = officialBuildsPushed.map(a => fmtCpuApk(a.cpu, a.apk));
    selApk.innerHTML = '';
    selApk.appendChild(
        buildOptions([...new Set([...mainApks, ...canaryApks])]));
  }

  /**
   * @param {string} v1
   * @param {string} v2
   * @return {number}
   */
  function compareVersions(v1, v2) {
    function toNumber(s) {
      return (
          s.split('.').map(x => parseInt(x)).reduce((x, y) => x * 1000 + y));
    }
    return toNumber(v1) - toNumber(v2);
  }

  function updateVersions() {
    const prev = selVersion1.value;
    // For the selected APK
    let mainVersions = milestonesPushed.version;
    let canaryVersions =
        (officialBuildsPushed
             .filter(a => fmtCpuApk(a.cpu, a.apk) === selApk.value)
             .map(a => a.version + ' (canary)'));

    if (selApk.value.indexOf('AndroidWebview.apk') !== -1) {
      // AndroidWebview.apk size information exists only for M71 and above.
      mainVersions =
          mainVersions.filter(v2 => compareVersions(v2, '71.0.0.0') > 0);
    }

    if (showAll.checked) {
      activeVersions = [...mainVersions, ...canaryVersions];
      activeVersions.sort(compareVersions);
    } else {
      canaryVersions.sort(compareVersions);
      activeVersions = [...mainVersions];
      if (canaryVersions.length) {
        activeVersions.push(canaryVersions[canaryVersions.length - 1]);
      }
    }
    selVersion1.innerHTML = '';
    selVersion1.appendChild(buildOptions(activeVersions));
    // Selects latest version (index -1) if previous option not still in list.
    selectOption(
        /** @type {NodeListOf<HTMLOptionElement>} */ (
            selVersion1.querySelectorAll('option')),
        activeVersions.indexOf(prev));
  }

  function updateDiffVersions() {
    // Filter diff-against versions that are newer
    // Preserve current options if possible
    const prev = selVersion2.value;
    selVersion2.innerHTML = '';
    let v1 = selVersion1.value;
    if (v1) {
      let diffVersions =
          activeVersions.filter(v2 => compareVersions(v2, v1) < 0);
      diffVersions.push(DO_NOT_DIFF);
      selVersion2.appendChild(buildOptions(diffVersions));
      selectOption(
          /** @type {NodeListOf<HTMLOptionElement>} */ (
              selVersion2.querySelectorAll('option')),
          diffVersions.indexOf(prev));
    }
  }

  updateApk();
  updateVersions();
  updateDiffVersions();

  selApk.addEventListener('change', () => {
    updateVersions();
  });

  selVersion1.addEventListener('change', () => {
    updateDiffVersions();
  });

  showAll.addEventListener('click', () => {
    updateApk();
    updateVersions();
    updateDiffVersions();
  });

  /** @return {string} */
  function getDataUrl() {
    function sizeUrlFor(value) {
      if (value.indexOf('canary') !== -1) {
        const strippedVersion = value.replace(/[^\d.]/g, '');
        return `${SIZE_FILEHOST}/official_builds/reports/${strippedVersion}/${
            selApk.value}.size`;
      }
      return `${SIZE_FILEHOST}/milestones/${value}/${selApk.value}.size`;
    }
    let ret = sizeUrlFor(selVersion1.value);
    if (selVersion2.value !== DO_NOT_DIFF) {
      ret += '&before_url=' + sizeUrlFor(selVersion2.value);
    }
    return ret;
  }

  setSubmitListener(form, getDataUrl);
})();