chromium/chrome/browser/resources/settings/site_settings/site_settings_mixin.ts

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

/**
 * @fileoverview Behavior common to Site Settings classes.
 */

// clang-format off
import type { PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
import {dedupingMixin} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';

import type {ContentSettingsTypes} from './constants.js';
import {ContentSetting, SiteSettingSource} from './constants.js';
import type {RawSiteException,SiteException,SiteSettingsPrefsBrowserProxy} from './site_settings_prefs_browser_proxy.js';
import {SiteSettingsPrefsBrowserProxyImpl} from './site_settings_prefs_browser_proxy.js';
// clang-format on

type Constructor<T> = new (...args: any[]) => T;

export const SiteSettingsMixin = dedupingMixin(
    <T extends Constructor<PolymerElement>>(superClass: T): T&
    Constructor<SiteSettingsMixinInterface> => {
      class SiteSettingsMixin extends superClass {
        static get properties() {
          return {
            /**
             * The string ID of the category this element is displaying data
             * for. See site_settings/constants.js for possible values.
             */
            category: String,

            /**
             * A cached list of ContentSettingsTypes with a standard
             * allow-block-ask pattern that are currently enabled for use. This
             * property is the same across all elements with SiteSettingsMixin
             * ('static').
             */
            contentTypes_: {
              type: Array,
              value: [],
            },
          };
        }

        category: ContentSettingsTypes;
        private contentTypes_: ContentSettingsTypes[];
        browserProxy: SiteSettingsPrefsBrowserProxy;

        constructor(...args: any[]) {
          super(...args);

          /**
           * The browser proxy used to retrieve and change information about
           * site settings categories and the sites within.
           */
          this.browserProxy = SiteSettingsPrefsBrowserProxyImpl.getInstance();
        }

        /**
         * Ensures the URL has a scheme (assumes http if omitted).
         * @param url The URL with or without a scheme.
         * @return The URL with a scheme, or an empty string.
         */
        ensureUrlHasScheme(url: string): string {
          if (url.length === 0) {
            return url;
          }
          return url.includes('://') ? url : 'http://' + url;
        }

        /**
         * Removes redundant ports, such as port 80 for http and 443 for https.
         * @param url The URL to sanitize.
         * @return The URL without redundant ports, if any.
         */
        sanitizePort(url: string): string {
          const urlWithScheme = this.ensureUrlHasScheme(url);
          if (urlWithScheme.startsWith('https://') &&
              urlWithScheme.endsWith(':443')) {
            return url.slice(0, -4);
          }
          if (urlWithScheme.startsWith('http://') &&
              urlWithScheme.endsWith(':80')) {
            return url.slice(0, -3);
          }
          return url;
        }

        /**
         * @return true if the passed content setting is considered 'enabled'.
         */
        computeIsSettingEnabled(setting: ContentSetting): boolean {
          return setting !== ContentSetting.BLOCK;
        }

        /**
         * Converts a string origin/pattern to a URL.
         * @param originOrPattern The origin/pattern to convert to URL.
         * @return The URL to return (or null if origin is not a valid URL).
         */
        toUrl(originOrPattern: string): URL|null {
          if (originOrPattern.length === 0) {
            return null;
          }
          // TODO(finnur): Hmm, it would probably be better to ensure scheme on
          // the JS/C++ boundary.
          // TODO(dschuyler): I agree. This filtering should be done in one go,
          // rather that during the sort. The URL generation should be wrapped
          // in a try/catch as well.
          originOrPattern = originOrPattern.replace('*://', '');
          originOrPattern = originOrPattern.replace('[*.]', '');
          return new URL(this.ensureUrlHasScheme(originOrPattern));
        }

        /**
         * @return a user-friendly name for the origin.
         */
        originRepresentation(origin: string): string {
          try {
            const url = this.toUrl(origin);
            return url ? (url.host || url.origin) : '';
          } catch (error) {
            return '';
          }
        }

        /**
         * Convert an exception (received from the C++ handler) to a full
         * SiteException.
         * @param exception The raw site exception from C++.
         * @return The expanded (full) SiteException.
         */
        expandSiteException(exception: RawSiteException): SiteException {
          const origin = exception.origin;
          const embeddingOrigin = exception.embeddingOrigin;

          // TODO(patricialor): |exception.source| should be one of the values
          // defined in |SiteSettingSource|.
          let enforcement = null;
          if (exception.source === SiteSettingSource.EXTENSION ||
              exception.source === SiteSettingSource.HOSTED_APP ||
              exception.source === SiteSettingSource.POLICY) {
            enforcement = chrome.settingsPrivate.Enforcement.ENFORCED;
          }

          let controlledBy = chrome.settingsPrivate.ControlledBy.PRIMARY_USER;
          if (exception.source === SiteSettingSource.EXTENSION ||
              exception.source === SiteSettingSource.HOSTED_APP) {
            controlledBy = chrome.settingsPrivate.ControlledBy.EXTENSION;
          } else if (exception.source === SiteSettingSource.POLICY) {
            controlledBy = chrome.settingsPrivate.ControlledBy.USER_POLICY;
          }

          return {
            category: exception.type as ContentSettingsTypes,
            embeddingOrigin: embeddingOrigin,
            incognito: exception.incognito,
            isEmbargoed: exception.isEmbargoed,
            origin: origin,
            displayName: exception.displayName,
            setting: exception.setting,
            description: exception.description,
            enforcement: enforcement,
            controlledBy: controlledBy,
          };
        }
      }

      return SiteSettingsMixin;
    });

export interface SiteSettingsMixinInterface {
  browserProxy: SiteSettingsPrefsBrowserProxy;
  category: ContentSettingsTypes;
  computeIsSettingEnabled(setting: string): boolean;
  originRepresentation(origin: string): string;
  toUrl(originOrPattern: string): URL|null;
  expandSiteException(exception: RawSiteException): SiteException;
  sanitizePort(url: string): string;
}