chromium/chrome/browser/resources/location_internals/location_internals.ts

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

import './diagnose_info_view.js';

import {getRequiredElement} from 'chrome://resources/js/util.js';

import type {DiagnoseInfoViewElement} from './diagnose_info_view.js';
import type {AccessPointData, GeolocationDiagnostics, GeolocationInternalsObserverInterface, NetworkLocationResponse} from './geolocation_internals.mojom-webui.js';
import {GeolocationInternalsObserverReceiver, GeolocationInternalsRemote} from './geolocation_internals.mojom-webui.js';
import {LocationInternalsHandler} from './location_internals.mojom-webui.js';

export const WATCH_BUTTON_ID = 'watch-btn';
export const LOG_BUTTON_ID = 'log-btn';
export const REFRESH_STATUS_ID = 'refresh-status';
export const REFRESH_STATUS_SUCCESS = 'Last updated ';
export const REFRESH_STATUS_UNINITIALIZED =
    `Geolocation API is not initialized. Click "Start Watching Position" to
     begin.`;
export const REFRESH_FINISH_EVENT = 'refresh-finish-event';
export const DIAGNOSE_INFO_VIEW_ID = 'diagnose-info-view';

let watchId: number = -1;
let geolocationInternals: GeolocationInternalsRemote|undefined;
let geolocationInternalsObserver: GeolocationInternalsObserverReceiver|
    undefined;
const diagnoseInfoView =
    getRequiredElement<DiagnoseInfoViewElement>(DIAGNOSE_INFO_VIEW_ID);

class GeolocationInternalsObserver implements
    GeolocationInternalsObserverInterface {
  onDiagnosticsChanged(diagnostics: GeolocationDiagnostics) {
    handleDiagnosticsChanged(diagnostics);
  }
  onNetworkLocationRequested(request: AccessPointData[]) {
    handleNetworkLocationRequested(request);
  }
  onNetworkLocationReceived(response: NetworkLocationResponse|null) {
    handleNetworkLocationReceived(response);
  }
}

// Initialize buttons callback
function initializeButtons() {
  const watchButton = getRequiredElement<HTMLElement>(WATCH_BUTTON_ID);
  watchButton.addEventListener('click', watchPosition);
  const saveButton = getRequiredElement<HTMLElement>(LOG_BUTTON_ID);
  saveButton.addEventListener('click', saveDiagnostics);
}

// Initialize Mojo pipe
export function initializeMojo() {
  geolocationInternals = new GeolocationInternalsRemote();
  LocationInternalsHandler.getRemote().bindInternalsInterface(
      geolocationInternals.$.bindNewPipeAndPassReceiver());

  geolocationInternalsObserver = new GeolocationInternalsObserverReceiver(
      new GeolocationInternalsObserver());
  geolocationInternals!
      .addInternalsObserver(
          geolocationInternalsObserver.$.bindNewPipeAndPassRemote())
      .then(data => {
        handleDiagnosticsChanged(data.diagnostics);
      });
}

function watchPosition() {
  const watchButton = getRequiredElement<HTMLElement>(WATCH_BUTTON_ID);
  if (watchId === -1) {
    watchId = navigator.geolocation.watchPosition(
        diagnoseInfoView.watchPositionSuccess,
        diagnoseInfoView.watchPositionError, {
          enableHighAccuracy: true,
          timeout: 5000,
          maximumAge: 0,
        });
    watchButton.textContent = 'Stop Watching Position';
  } else {
    navigator.geolocation.clearWatch(watchId);
    watchId = -1;
    watchButton.textContent = 'Start Watching Position';
  }
}

function handleDiagnosticsChanged(diagnostics: GeolocationDiagnostics|null) {
  const refreshStatus = getRequiredElement(REFRESH_STATUS_ID);
  if (diagnostics) {
    refreshStatus.textContent =
        REFRESH_STATUS_SUCCESS + new Date().toLocaleString();
    diagnoseInfoView.updateDiagnosticsTables(diagnostics);
  } else {
    refreshStatus.textContent = REFRESH_STATUS_UNINITIALIZED;
  }
  window.dispatchEvent(new CustomEvent(REFRESH_FINISH_EVENT));
}

function handleNetworkLocationRequested(request: AccessPointData[]) {
  diagnoseInfoView.updateLastNetworkRequestTable(request);
  window.dispatchEvent(new CustomEvent(REFRESH_FINISH_EVENT));
}

function handleNetworkLocationReceived(response: NetworkLocationResponse|null) {
  diagnoseInfoView.updateLastNetworkResponseTable(response);
  window.dispatchEvent(new CustomEvent(REFRESH_FINISH_EVENT));
}

function saveDiagnostics() {
  const tables = diagnoseInfoView.outputTables();
  const content = JSON.stringify(tables, null, 2);
  const blob = new Blob([content], {type: 'application/json'});
  const url = URL.createObjectURL(blob);
  const a = document.createElement('a');
  a.href = url;
  a.download = `location_internals_${new Date().toISOString()}.json`;
  a.click();
}

document.addEventListener('DOMContentLoaded', () => {
  initializeButtons();
  initializeMojo();
});