chromium/chrome/browser/resources/extensions/site_permissions/site_permissions_edit_url_dialog.ts

// Copyright 2022 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_button/cr_button.js';
import 'chrome://resources/cr_elements/cr_dialog/cr_dialog.js';
import 'chrome://resources/cr_elements/cr_input/cr_input.js';
import 'chrome://resources/cr_elements/cr_shared_style.css.js';
import '../strings.m.js';

import type {CrButtonElement} from 'chrome://resources/cr_elements/cr_button/cr_button.js';
import type {CrDialogElement} from 'chrome://resources/cr_elements/cr_dialog/cr_dialog.js';
import {assert} from 'chrome://resources/js/assert.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 {getTemplate} from './site_permissions_edit_url_dialog.html.js';
import type {SiteSettingsDelegate} from './site_settings_mixin.js';

// A RegExp to roughly match acceptable patterns entered by the user.
// exec'ing() this RegExp will match the following groups:
// 0: Full matched string.
// 1: Scheme + scheme separator (e.g., 'https://').
// 2: Scheme only (e.g., 'https').
// 3: Hostname (e.g., 'example.com').
// 4: Port, including ':' separator (e.g., ':80').
export const sitePermissionsPatternRegExp = new RegExp(
    '^' +
    // Scheme; optional.
    '((http|https)://)?' +
    // Hostname or localhost, required.
    '([a-z0-9\\.-]+\\.[a-z0-9]+|localhost)' +
    // Port, optional.
    '(:[0-9]+)?' +
    '$');

export function getSitePermissionsPatternFromSite(site: string): string {
  const res = sitePermissionsPatternRegExp.exec(site)!;
  assert(res);
  const scheme = res[1] || 'https://';
  const host = res[3];
  const port = res[4] || '';
  return scheme + host + port;
}

export interface SitePermissionsEditUrlDialogElement {
  $: {
    dialog: CrDialogElement,
    submit: CrButtonElement,
  };
}

export class SitePermissionsEditUrlDialogElement extends PolymerElement {
  static get is() {
    return 'site-permissions-edit-url-dialog';
  }

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

  static get properties() {
    return {
      delegate: Object,
      siteSet: String,

      /**
       * The site that this entry is currently managing. Only non-empty if this
       * is for editing an existing entry.
       */
      siteToEdit: {
        type: String,
        value: null,
      },

      site_: {
        type: String,
        value: '',
      },

      /** Whether the currently-entered input is valid. */
      inputValid_: {
        type: Boolean,
        value: true,
      },
    };
  }

  delegate: SiteSettingsDelegate;
  siteSet: chrome.developerPrivate.SiteSet;
  siteToEdit: string|null;
  private site_: string;
  private inputValid_: boolean;

  override connectedCallback() {
    super.connectedCallback();

    if (this.siteToEdit !== null) {
      this.site_ = this.siteToEdit;
      this.validate_();
    }
  }

  /**
   * Validates that the pattern entered is valid by testing it against the
   * regex. An empty patterh is considered "valid" as the invalid message will
   * not be shown, but the input cannot be submitted as the action button will
   * be disabled.
   */
  private validate_() {
    this.inputValid_ = this.site_.trim().length === 0 ||
        sitePermissionsPatternRegExp.test(this.site_);
  }

  private computeDialogTitle_(): string {
    return loadTimeData.getString(
        this.siteToEdit === null ? 'sitePermissionsAddSiteDialogTitle' :
                                   'sitePermissionsEditSiteDialogTitle');
  }

  private computeSubmitButtonDisabled_(): boolean {
    // If input is empty, disable the action button.
    return !this.inputValid_ || this.site_.trim().length === 0;
  }

  private computeSubmitButtonLabel_(): string {
    return loadTimeData.getString(this.siteToEdit === null ? 'add' : 'save');
  }

  private onCancel_() {
    this.$.dialog.cancel();
  }

  private onSubmit_() {
    const pattern = getSitePermissionsPatternFromSite(this.site_);
    if (this.siteToEdit !== null) {
      this.handleEdit_(pattern);
    } else {
      this.handleAdd_(pattern);
    }
  }

  private handleEdit_(pattern: string) {
    assert(this.siteToEdit);
    if (pattern === this.siteToEdit) {
      this.$.dialog.close();
      return;
    }

    this.delegate.removeUserSpecifiedSites(this.siteSet, [this.siteToEdit])
        .then(() => {
          this.addUserSpecifiedSite_(pattern);
        });
  }

  private handleAdd_(pattern: string) {
    assert(!this.siteToEdit);
    this.addUserSpecifiedSite_(pattern);
  }

  private addUserSpecifiedSite_(pattern: string) {
    this.delegate.addUserSpecifiedSites(this.siteSet, [pattern])
        .then(
            () => {
              this.$.dialog.close();
            },
            () => {
              this.inputValid_ = false;
            });
  }
}

declare global {
  interface HTMLElementTagNameMap {
    'site-permissions-edit-url-dialog': SitePermissionsEditUrlDialogElement;
  }
}

customElements.define(
    SitePermissionsEditUrlDialogElement.is,
    SitePermissionsEditUrlDialogElement);