blob: 2b57bf2a681d6ca06053dda85a338764188e1b52 [file] [log] [blame]
// Copyright 2019 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.
'use strict';
/**
* @typedef {{
* sitelist: Array<string>,
* greylist: Array<string>,
* }}
*/
let RuleSet;
/**
* @typedef {{
* gpo: RuleSet,
* ieem: (RuleSet|undefined),
* external: (RuleSet|undefined),
* }}
*/
let RuleSetList;
/**
* Returned by getRulesetSources().
* @typedef {{
* browser_switcher: Object<string, string>!,
* }}
*/
let RulesetSources;
/**
* Returned by getTimestamps().
* @typedef {{
* last_fetch: number,
* next_fetch: number,
* }}
*/
let TimestampPair;
/**
* Converts 'this_word' to 'ThisWord'
* @param {string} symbol
* @return {string}
*/
function snakeCaseToUpperCamelCase(symbol) {
if (!symbol) {
return symbol;
}
return symbol.replace(/(?:^|_)([a-z])/g, (_, letter) => {
return letter.toUpperCase();
});
}
/**
* Clears the table, and inserts a header row.
* @param {HTMLTableElement} table
* @param {HTMLTemplateElement} headerTemplate
* Template to use to re-create the header row.
*/
function clearTable(table, headerTemplate) {
table.innerHTML = '';
const headerRow = document.importNode(headerTemplate.content, true);
table.appendChild(headerRow);
}
/**
* @param {string} rule
* @return {string} String describing the rule type.
*/
function getRuleType(rule) {
if (rule == '*') {
return 'wildcard';
}
if (rule.includes('/')) {
return 'prefix';
}
return 'hostname';
}
/**
* Creates and returns a <tr> element for the given rule.
* @param {string} rule
* @param {string} rulesetName
* @return {HTMLTableRowElement}
*/
function createRowForRule(rule, rulesetName) {
const row = document.importNode($('rule-row-template').content, true);
const cells = row.querySelectorAll('td');
cells[0].innerText = rule;
cells[0].className = 'url';
cells[1].innerText = rulesetName;
cells[2].innerText = getRuleType(rule);
cells[3].innerText = /^!/.test(rule) ? 'yes' : 'no';
return /** @type {HTMLTableRowElement} */ (row);
}
/**
* Updates the content of all tables after receiving data from the backend.
* @param {RuleSetList} rulesets
*/
function updateTables(rulesets) {
const headerTemplate =
/** @type {HTMLTemplateElement} */ ($('header-row-template'));
clearTable(/** @type {HTMLTableElement} */ ($('sitelist')), headerTemplate);
clearTable(/** @type {HTMLTableElement} */ ($('greylist')), headerTemplate);
for (const [rulesetName, ruleset] of Object.entries(rulesets)) {
for (const [listName, rules] of Object.entries(ruleset)) {
const table = $(listName);
for (const rule of rules) {
table.appendChild(createRowForRule(rule, rulesetName));
}
}
}
}
function checkUrl() {
const url = $('url-checker-input').value;
if (!url) {
$('output').innerText = '';
return;
}
cr.sendWithPromise('getDecision', url)
.then(decision => {
// URL is valid.
$('output').innerText = JSON.stringify(decision, null, 2);
})
.catch(err => {
// URL is invalid.
$('output').innerText =
'Invalid URL. Make sure it is formatted properly.';
});
}
$('url-checker-input').addEventListener('input', checkUrl);
/**
* Formats |date| as "HH:MM:SS".
* @param {Date} date
* @return {string}
*/
function formatTime(date) {
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}`;
}
/**
* Update the paragraphs under the "XML sitelists" section.
* @param {TimestampPair?} timestamps
*/
function updateTimestamps(timestamps) {
if (!timestamps) {
return;
}
const lastFetch = new Date(timestamps.last_fetch);
const nextFetch = new Date(timestamps.next_fetch);
if (lastFetch.valueOf() == 0) {
// Not fetched yet.
$('xml-not-fetched-yet').style.display = 'block';
$('xml-last-fetch').style.display = 'none';
} else {
// Already fetched.
$('xml-not-fetched-yet').style.display = 'none';
$('xml-last-fetch').style.display = 'block';
}
$('xml-next-fetch').style.display = nextFetch.valueOf() ? 'block' : 'none';
$('last-fetch-placeholder').innerText = formatTime(lastFetch);
$('next-fetch-placeholder').innerText = formatTime(nextFetch);
}
/**
* Update the table under the "XML sitelists" section.
* @param {RulesetSources} sources
*/
function updateXmlTable({browser_switcher: sources}) {
const headerTemplate =
/** @type {HTMLTemplateElement} */ ($('xml-header-row-template'));
clearTable(
/** @type {HTMLTableElement} */ ($('xml-sitelists')), headerTemplate);
for (const [prefName, url] of Object.entries(sources)) {
// Hacky: guess the policy name from the pref name by converting 'foo_bar'
// to 'BrowserSwitcherFooBar'. This relies on prefs having the same name as
// the associated policy.
const policyName = 'BrowserSwitcher' + snakeCaseToUpperCamelCase(prefName);
const row = document.importNode($('xml-row-template').content, true);
const cells = row.querySelectorAll('td');
cells[0].innerText = policyName;
cells[1].innerText = url || '(not configured)';
cells[1].className = 'url';
$('xml-sitelists').appendChild(row);
}
// Hide/show the description paragraphs depending on whether any XML sitelist
// is configured.
const enabled = Object.values(sources).some(x => !!x);
$('xml-description-wrapper').style.display = enabled ? 'block' : 'none';
}
/**
* Called by C++ when we need to update everything on the page.
*/
function updateEverything() {
cr.sendWithPromise('getAllRulesets').then(updateTables);
cr.sendWithPromise('getTimestamps').then(updateTimestamps);
cr.sendWithPromise('getRulesetSources').then(updateXmlTable);
checkUrl();
}
// TODO(crbug/959379): Keep the page up-to-date at all times: updateEverything()
// when LBS prefs are updated, and after sitelists get refreshed.
updateEverything();
$('refresh-xml-button').addEventListener('click', () => {
chrome.send('refreshXml');
});