// 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;
}