blob: 0fb56910c1f2ebc6387d47ad7bed0952e1b83675 [file] [log] [blame]
// Copyright 2021 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import '../strings.m.js';
import 'chrome://resources/cr_elements/cr_toolbar/cr_toolbar.js';
import {addWebUIListener} from 'chrome://resources/js/cr.m.js';
import {I18nMixin} from 'chrome://resources/js/i18n_mixin.js';
import {loadTimeData} from 'chrome://resources/js/load_time_data.m.js';
import {PolymerElement,} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
import {getTemplate} from './app.html.js';
import {BrowserSwitchInternalsProxy, BrowserSwitchInternalsProxyImpl, Decision, RuleSet, RuleSetList, RulesetSources, TimestampPair,} from './browser_switch_internals_proxy.js';
const BrowserSwitchInternalsAppElementBase = I18nMixin(PolymerElement);
class BrowserSwitchInternalsAppElement extends
BrowserSwitchInternalsAppElementBase {
static get is() {
return 'browser-switch-internals-app';
}
static get template() {
return getTemplate();
}
static get properties() {
return {
isBrowserSwitcherEnabled_: {
type: Boolean,
value: true,
},
showSearch_: {
type: Boolean,
value: false,
},
lastFetch_: {
type: String,
value: '',
},
nextFetch_: {
type: String,
value: '',
},
urlCheckerInput_: {
type: String,
observer: 'checkUrl_',
},
urlCheckerOutput_: {
type: Array,
value: [],
},
browserName_: {
type: String,
value: getBrowserName,
},
altBrowserName_: {
type: String,
value: getAltBrowserName,
},
greyListRules_: {
type: Array,
value: [],
},
siteListRules_: {
type: Array,
value: [],
},
xmlSiteLists_: {
type: Array,
value: [],
},
};
}
private isBrowserSwitcherEnabled_: boolean;
private showSearch_: boolean;
private browserName_: string;
private altBrowserName_: string;
private greyListRules_: object[];
private siteListRules_: object[];
private xmlSiteLists_: object[];
private urlCheckerInput_: string;
private urlCheckerOutput_: string[];
private lastFetch_: string;
private nextFetch_: string;
override ready() {
super.ready();
this.updateEverything();
document.addEventListener('DOMContentLoaded', () => {
addWebUIListener('data-changed', () => this.updateEverything());
});
}
getRuleBrowserName(rule: string) {
return rule.startsWith('!') ? getBrowserName() : getAltBrowserName();
}
getPolicyFromRuleset(ruleSetName: string) {
const rulesetToPolicy: Record<string, string> = {
gpo: 'BrowserSwitcherUrlList',
ieem: 'BrowserSwitcherUseIeSitelist',
external_sitelist: 'BrowserSwitcherExternalSitelistUrl',
external_greylist: 'BrowserSwitcherExternalGreylistUrl',
};
return rulesetToPolicy[ruleSetName];
}
/**
* Updates the content of all tables after receiving data from the backend.
*/
updateTables(rulesets: RuleSetList) {
const listNameToProperty: Record<string, string> = {
sitelist: 'siteListRules_',
greylist: 'greyListRules_',
};
this.siteListRules_ = [];
this.greyListRules_ = [];
for (const [rulesetName, ruleset] of Object.entries(rulesets)) {
for (const [listName, rules] of Object.entries(ruleset as RuleSet)) {
this.push(listNameToProperty[listName], ...rules.map((rule) => ({
rulesetName,
rule,
})));
}
}
}
/**
* Takes the json from the url checker and makes it readable.
*/
urlOutputText(decision: Decision): string[] {
let opensIn = '';
const altBrowserName = getAltBrowserName();
const browserName = getBrowserName();
switch (decision.action) {
case 'stay':
opensIn = this.i18n('openBrowser', browserName) + '\n';
break;
case 'go':
opensIn = this.i18n('openBrowser', altBrowserName) + '\n';
break;
}
let reason = '';
if (decision.matching_rule) {
if (decision.matching_rule.startsWith('!')) {
reason += this.i18n(
'openBrowserInvertRuleReason',
JSON.stringify(decision.matching_rule)) +
'\n';
} else {
const list = decision.reason === 'sitelist' ?
this.i18n('forceOpenTitle') :
this.i18n('ignoreTitle');
reason += this.i18n(
'openBrowserRuleReason',
JSON.stringify(decision.matching_rule), list) +
'\n';
}
}
// if undefined - add nothing to the output
switch (decision.reason) {
case 'globally_disabled':
throw new Error('BrowserSwitcherEnabled policy is set as false!');
case 'protocol':
reason += this.i18n('openBrowserProtocolReason') + '\n';
break;
case 'default':
reason += this.i18n('openBrowserDefaultReason', browserName) + '\n';
break;
}
return [opensIn, reason];
}
private checkUrl_(url: string) {
if (!url) {
this.urlCheckerOutput_ = [];
return;
}
if (!url.includes('://')) {
url = 'http://' + url;
}
getProxy()
.getDecision(url)
.then((decision) => {
// URL is valid.
this.urlCheckerOutput_ = this.urlOutputText(decision);
})
.catch((errorMessage) => {
// URL is invalid.
console.warn(errorMessage);
this.urlCheckerOutput_ = [this.i18n('invalidURL')];
});
}
refreshXml() {
getProxy().refreshXml();
}
/**
* Update the paragraphs under the "XML sitelists" section.
*/
updateTimestamps(timestamps: TimestampPair|null) {
if (!timestamps) {
return;
}
const {last_fetch, next_fetch} = timestamps;
this.lastFetch_ = last_fetch !== 0 ? formatTime(last_fetch) : '';
this.nextFetch_ = next_fetch !== 0 ? formatTime(next_fetch) : '';
}
/**
* Update the table under the "XML sitelists" section.
*/
updateXmlTable(rulesetSources: RulesetSources) {
this.xmlSiteLists_ = [];
this.push(
'xmlSiteLists_',
...Object.entries(rulesetSources)
.map(([prefName, url]) => ({
// Hacky name guessing
policyName: 'BrowserSwitcher' +
snakeCaseToUpperCamelCase(prefName.split('.')[1]),
url: url || this.i18n('notConfigured'),
})));
}
/**
* Called by C++ when we need to update everything on the page.
*/
async updateEverything() {
this.isBrowserSwitcherEnabled_ =
await getProxy().isBrowserSwitcherEnabled();
if (this.isBrowserSwitcherEnabled_) {
getProxy().getAllRulesets().then(
(rulesets) => this.updateTables(rulesets));
getProxy().getTimestamps().then(
(timestamps) => this.updateTimestamps(timestamps));
getProxy().getRulesetSources().then(
(sources) => this.updateXmlTable(sources));
}
}
/**
* Section: XML configuration source
* Shows information about the last time XML sitelists were downloaded.
*/
private getXMLSitelistsLastDownloadLabel(): string {
return this.i18n('xmlSitelistLastDownloadDate', this.lastFetch_);
}
/**
* Section: XML configuration source
* Shows information about the next download time of XML sitelists.
*/
private getXMLSitelistsNextDownloadLabel(): string {
return this.i18n('xmlSitelistNextDownloadDate', this.nextFetch_);
}
/**
* Section: Ignore
* Paragraph that informs that the URLs that are affected by the lists
* BrowserSwitcherExternalGreylistUrl and BrowserSwitcherUrlGreylist
* will not trigger a browser switch.
*/
private getIgnoreURLMatchingLabel(): string {
return this.i18n('ignoreParagraph2', getBrowserName(), getAltBrowserName());
}
}
customElements.define(
BrowserSwitchInternalsAppElement.is, BrowserSwitchInternalsAppElement);
/**
* Converts 'this_word' to 'ThisWord'
*/
function snakeCaseToUpperCamelCase(symbol: string): string {
if (!symbol) {
return symbol;
}
return symbol.replace(/(?:^|_)([a-z])/g, (_, letter) => {
return letter.toUpperCase();
});
}
/**
* Formats |date| as "HH:MM:SS".
*/
function formatTime(dateNumber: number): string {
const date = new Date(dateNumber);
const hh = date.getHours().toString().padStart(2, '0');
const mm = date.getMinutes().toString().padStart(2, '0');
const ss = date.getSeconds().toString().padStart(2, '0');
return `${hh}:${mm}:${ss}`;
}
/**
* Gets the English name of the alternate browser.
*/
function getAltBrowserName(): string {
// TODO (crbug.com/1258133): if you change the AlternativeBrowserPath
// policy, then loadTimeData can contain stale data. It won't update
// until you refresh (despite the rest of the page auto-updating).
return loadTimeData.getString('altBrowserName') || 'alternative browser';
}
/**
* Gets the English name of the browser.
*/
function getBrowserName(): string {
// TODO (crbug.com/1258133): if you change the AlternativeBrowserPath
// policy, then loadTimeData can contain stale data. It won't update
// until you refresh (despite the rest of the page auto-updating).
return loadTimeData.getString('browserName');
}
function getProxy(): BrowserSwitchInternalsProxy {
return BrowserSwitchInternalsProxyImpl.getInstance();
}