chromium/chrome/browser/resources/dlp_internals/dlp_internals_ui.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 'chrome://resources/cr_elements/cr_page_selector/cr_page_selector.js';
import 'chrome://resources/cr_elements/cr_tabs/cr_tabs.js';
import '//resources/cr_elements/cr_collapse/cr_collapse.js';
import '//resources/cr_elements/cr_expand_button/cr_expand_button.js';
import './strings.m.js';

import {loadTimeData} from 'chrome://resources/js/load_time_data.js';
import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';

import type {ContentRestriction, DataTransferEndpoint, DlpEvent, DlpEvent_Mode, DlpEvent_Restriction, DlpEvent_UserType, EndpointType, EventDestination, FileDatabaseEntry, Level, PageHandlerInterface, WebContentsInfo} from './dlp_internals.mojom-webui.js';
import {PageHandler, ReportingObserverReceiver} from './dlp_internals.mojom-webui.js';
import {getTemplate} from './dlp_internals_ui.html.js';
import {ContentRestrictionMap, DestinationComponentMap, EndpointTypeMap, EventModeMap, EventRestrictionMap, EventUserTypeMap, LevelMap, WebContentsElement} from './dlp_utils.js';

// Polymer element DLP Internals UI.
class DlpInternalsUi extends PolymerElement {
  static get is() {
    return 'dlp-internals-ui';
  }

  static get template() {
    return getTemplate();
  }

  static get properties() {
    return {
      isOtr_: Boolean,
      doRulesManagerExist_: Boolean,
      showTabs_: Boolean,
      selectedTabIdx_: Number,
      tabNames_: Array,
      clipboardSourceType_: String,
      clipboardSourceUrl_: String,
      reportingEvents_: Array,
      webContentsElements_: Array,
    };
  }

  override connectedCallback() {
    super.connectedCallback();

    this.fetchClipboardSourceInfo();
    this.fetchContentRestrictionsInfo();
    this.fetchFilesDatabaseEntries();
  }

  // Names of the top level page tabs.
  private tabNames_: string[] = [
    'Clipboard',
    'OnScreen',
    'Files',
    'Reporting',
  ];

  // Whether the profle is off the record.
  private isOtr_: boolean = false;

  // Whether DLP rules manager exists.
  private doRulesManagerExist_: boolean = false;

  // Whether the page tabs show be added.
  private showTabs_: boolean = false;

  // Index of the selected top level page tab.
  private selectedTabIdx_: number = 0;

  // Clipboard source type string.
  private clipboardSourceType_: string;

  // Clipboard source url.
  private clipboardSourceUrl_: string;

  // Reporting events array.
  private reportingEvents_: DlpEvent[] = [];

  // Web Contents Info.
  private webContentsElements_: WebContentsElement[] = [];

  // Files Database Entries.
  private filesEntries_: FileDatabaseEntry[] = [];

  // Selected file inode number.
  private selectedFileInode_: bigint;

  private readonly pageHandler_: PageHandlerInterface;
  private readonly reportingObserver_: ReportingObserverReceiver;

  constructor() {
    super();

    if (loadTimeData.valueExists('isOtr')) {
      this.isOtr_ = loadTimeData.getBoolean('isOtr');
    }

    if (loadTimeData.valueExists('doRulesManagerExist')) {
      this.doRulesManagerExist_ =
          loadTimeData.getBoolean('doRulesManagerExist');
    }

    this.showTabs_ = !this.isOtr_ && this.doRulesManagerExist_;

    this.pageHandler_ = PageHandler.getRemote();
    this.reportingObserver_ = new ReportingObserverReceiver(this);
    this.pageHandler_.observeReporting(
        this.reportingObserver_.$.bindNewPipeAndPassRemote());
  }

  private async fetchClipboardSourceInfo(): Promise<void> {
    this.pageHandler_.getClipboardDataSource()
        .then((value: {source: DataTransferEndpoint|null}) => {
          this.setClipboardInfo(value.source);
        })
        .catch((e: object) => {
          console.warn(`getClipboardDataSource failed: ${JSON.stringify(e)}`);
        });
  }

  private setClipboardInfo(source: DataTransferEndpoint|null|undefined) {
    if (!source) {
      this.clipboardSourceType_ = 'undefined';
      this.clipboardSourceUrl_ = 'undefined';
      return;
    }

    this.clipboardSourceType_ = `${this.endpointTypeToString(source.type)}`;
    if (source.url === null) {
      this.clipboardSourceUrl_ = 'undefined';
    } else {
      this.clipboardSourceUrl_ = source.url.url;
    }
  }

  private endpointTypeToString(type: EndpointType): string {
    return EndpointTypeMap[type] || 'invalid';
  }

  private async fetchContentRestrictionsInfo(): Promise<void> {
    this.pageHandler_.getContentRestrictionsInfo()
        .then((value: {webContentsInfo: WebContentsInfo[]}) => {
          this.setWebContentsInfo(value.webContentsInfo);
        })
        .catch((e: object) => {
          console.warn(
              `getContentRestrictionsInfo failed: ${JSON.stringify(e)}`);
        });
  }

  private setWebContentsInfo(webContentsInfo: WebContentsInfo[]) {
    this.webContentsElements_ = [];
    for (const info of webContentsInfo) {
      this.webContentsElements_.push(new WebContentsElement(info));
    }
    if (webContentsInfo.length) {
      this.notifySplices('webContentsElements_', [{
                           index: 0,
                           addedCount: this.webContentsElements_.length,
                           object: this.webContentsElements_,
                           type: 'splice',
                           removed: [],
                         }]);
    }
  }

  private async fetchFilesDatabaseEntries(): Promise<void> {
    this.pageHandler_.getFilesDatabaseEntries()
        .then((value: {dbEntries: FileDatabaseEntry[]}) => {
          this.setFilesDatabaseEntries(value.dbEntries);
        })
        .catch((e: object) => {
          console.warn(`getFilesDatabaseEntries failed: ${JSON.stringify(e)}`);
        });
  }

  private setFilesDatabaseEntries(entries: FileDatabaseEntry[]) {
    this.filesEntries_ = entries;
    if (entries.length) {
      this.notifySplices('filesEntries_', [{
                           index: 0,
                           addedCount: this.filesEntries_.length,
                           object: this.filesEntries_,
                           type: 'splice',
                           removed: [],
                         }]);
    }
  }

  /** Implements ReportingObserverInterface */
  onReportEvent(event: DlpEvent): void {
    this.reportingEvents_.push(event);
    this.notifySplices('reportingEvents_', [{
                         index: this.reportingEvents_.length - 1,
                         addedCount: 1,
                         object: this.reportingEvents_,
                         type: 'splice',
                         removed: [],
                       }]);
  }

  destinationToString(destination: EventDestination|null|undefined): string {
    if (destination) {
      if (destination.urlPattern) {
        return destination.urlPattern;
      }
      if (destination.component) {
        return DestinationComponentMap[destination.component];
      }
    }
    return 'undefined';
  }

  restrictionToString(restriction: DlpEvent_Restriction|null|
                      undefined): string {
    if (restriction) {
      return EventRestrictionMap[restriction];
    }
    return 'undefined';
  }

  modeToString(mode: DlpEvent_Mode|null|undefined): string {
    if (mode) {
      return EventModeMap[mode];
    }
    return 'undefined';
  }

  userTypeToString(userType: DlpEvent_UserType|null|undefined): string {
    if (userType) {
      return EventUserTypeMap[userType];
    }
    return 'undefined';
  }

  timestampToString(timestampMicro: bigint): string {
    if (timestampMicro) {
      const timestampMilli: number = Number(timestampMicro) / 1000;
      const timestamp: Date = new Date(timestampMilli);
      return timestamp.toLocaleString();
    }
    return 'undefined';
  }

  contentRestrictionToString(restriction: ContentRestriction): string {
    return ContentRestrictionMap[restriction];
  }

  levelToString(level: Level): string {
    return LevelMap[level];
  }

  creationTimeToString(timestampSeconds: bigint): string {
    if (timestampSeconds) {
      const timestampMilli: number = Number(timestampSeconds) * 1000;
      const timestamp: Date = new Date(timestampMilli);
      return timestamp.toLocaleString();
    }
    return 'undefined';
  }

  private onFileSelected(e: Event) {
    const selectedFile = (e.target as HTMLInputElement).value;
    this.pageHandler_.getFileInode(selectedFile.replace('C:\\fakepath\\', ''))
        .then((value: {inode: bigint|null}) => {
          if (value.inode) {
            this.selectedFileInode_ = value.inode;
          } else {
            this.selectedFileInode_ = BigInt(0);
          }
        })
        .catch((e: object) => {
          console.warn(`getFileInode failed: ${JSON.stringify(e)}`);
        });
  }
}

customElements.define(DlpInternalsUi.is, DlpInternalsUi);