blob: 09801125c467b9bcef0fc4eeb2fb7be05ba89856 [file] [log] [blame]
// Copyright 2017 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/mojo/url/mojom/origin.mojom-webui.js';
import {assert, assertNotReached} from 'chrome://resources/js/assert.js';
import {PromiseResolver} from 'chrome://resources/js/promise_resolver.js';
import type {MediaEngagementConfig, MediaEngagementScoreDetails, MediaEngagementScoreDetailsProviderRemote} from './media_engagement_score_details.mojom-webui.js';
import {MediaEngagementScoreDetailsProvider} from './media_engagement_score_details.mojom-webui.js';
// Allow a function to be provided by tests, which will be called when
// the page has been populated with media engagement details.
const pageIsPopulatedResolver = new PromiseResolver<void>();
function whenPageIsPopulatedForTest(): Promise<void> {
return pageIsPopulatedResolver.promise;
}
Object.assign(window, {whenPageIsPopulatedForTest});
let detailsProvider: MediaEngagementScoreDetailsProviderRemote|null = null;
let info: MediaEngagementScoreDetails[]|null = null;
let engagementTableBody: HTMLElement|null = null;
let sortReverse: boolean = true;
let sortKey: string = 'totalScore';
let configTableBody: HTMLElement|null = null;
let showNoPlaybacks: boolean = false;
/**
* Creates a single row in the engagement table.
*/
function createRow(rowInfo: MediaEngagementScoreDetails): DocumentFragment {
const template = document.querySelector<HTMLTemplateElement>('#datarow');
assert(template);
const td = template.content.querySelectorAll('td');
td[0].textContent = rowInfo.origin.scheme + '://' + rowInfo.origin.host;
if (rowInfo.origin.scheme === 'http' && rowInfo.origin.port !== 80) {
td[0].textContent += ':' + rowInfo.origin.port;
} else if (rowInfo.origin.scheme === 'https' && rowInfo.origin.port !== 443) {
td[0].textContent += ':' + rowInfo.origin.port;
}
td[1].textContent = rowInfo.visits.toString();
td[2].textContent = rowInfo.mediaPlaybacks.toString();
td[3].textContent = rowInfo.lastMediaPlaybackTime ?
new Date(rowInfo.lastMediaPlaybackTime).toISOString() :
'';
td[4].textContent = rowInfo.isHigh ? 'Yes' : 'No';
td[5].textContent = rowInfo.totalScore ? rowInfo.totalScore.toFixed(2) : '0';
td[6].querySelectorAll<HTMLElement>('.engagement-bar')[0].style.width =
(rowInfo.totalScore * 50) + 'px';
return document.importNode(template.content, true);
}
/**
* Remove all rows from the engagement table.
*/
function clearTable() {
assert(engagementTableBody);
engagementTableBody.innerHTML =
window.trustedTypes ? window.trustedTypes.emptyHTML : '';
}
/**
* Sort the engagement info based on |sortKey| and |sortReverse|.
*/
function sortInfo() {
assert(info);
info.sort((a, b) => {
return (sortReverse ? -1 : 1) * compareTableItem(sortKey, a, b);
});
}
/**
* Compares two MediaEngagementScoreDetails objects based on |sortKey|.
* @param sortKey The name of the property to sort by.
* @param a The first object to compare.
* @param b The second object to compare.
* @return A negative number if |a| should be ordered before
* |b|, a positive number otherwise.
*/
function compareTableItem(
sortKey: string, a: MediaEngagementScoreDetails,
b: MediaEngagementScoreDetails): number {
// Compare the hosts of the origin ignoring schemes.
if (sortKey === 'origin') {
return a.origin.host > b.origin.host ? 1 : -1;
}
if (sortKey === 'visits' || sortKey === 'mediaPlaybacks' ||
sortKey === 'lastMediaPlaybackTime' || sortKey === 'totalScore' ||
sortKey === 'audiblePlaybacks' || sortKey === 'significantPlaybacks' ||
sortKey === 'highScoreChanges' || sortKey === 'mediaElementPlaybacks' ||
sortKey === 'audioContextPlaybacks' || sortKey === 'isHigh') {
const val1 = (a as {[key: string]: any})[sortKey];
const val2 = (b as {[key: string]: any})[sortKey];
return (val1 as number) - (val2 as number);
}
assertNotReached('Unsupported sort key: ' + sortKey);
}
/**
* Creates a single row in the config table.
* @param name The name of the config setting.
* @param value The value of the config setting.
*/
function createConfigRow(name: string, value: number|string): DocumentFragment {
const template = document.querySelector<HTMLTemplateElement>('#configrow');
assert(template);
const td = template.content.querySelectorAll('td');
td[0].textContent = name;
td[1].textContent = value.toString();
return document.importNode(template.content, true);
}
/**
* Regenerates the config table.
* @param config The config of the MEI service.
*/
function renderConfigTable(config: MediaEngagementConfig) {
assert(configTableBody);
configTableBody.innerHTML =
window.trustedTypes ? (window.trustedTypes.emptyHTML) : '';
configTableBody.appendChild(
createConfigRow('Min Sessions', config.scoreMinVisits));
configTableBody.appendChild(
createConfigRow('Lower Threshold', config.highScoreLowerThreshold));
configTableBody.appendChild(
createConfigRow('Upper Threshold', config.highScoreUpperThreshold));
configTableBody.appendChild(createConfigRow(
'Record MEI data', formatFeatureFlag(config.featureRecordData)));
configTableBody.appendChild(createConfigRow(
'Bypass autoplay based on MEI',
formatFeatureFlag(config.featureBypassAutoplay)));
configTableBody.appendChild(createConfigRow(
'Preload MEI data', formatFeatureFlag(config.featurePreloadData)));
configTableBody.appendChild(createConfigRow(
'MEI for HTTPS only', formatFeatureFlag(config.featureHttpsOnly)));
configTableBody.appendChild(createConfigRow(
'Autoplay disable settings',
formatFeatureFlag(config.featureAutoplayDisableSettings)));
configTableBody.appendChild(createConfigRow(
'Unified autoplay (preference)',
formatFeatureFlag(config.prefDisableUnifiedAutoplay)));
configTableBody.appendChild(createConfigRow(
'Custom autoplay policy',
formatFeatureFlag(config.hasCustomAutoplayPolicy)));
configTableBody.appendChild(
createConfigRow('Autoplay Policy', config.autoplayPolicy));
configTableBody.appendChild(createConfigRow(
'Preload version',
config.preloadVersion ? config.preloadVersion : 'Not Available'));
}
/**
* Converts a boolean into a string value.
*/
function formatFeatureFlag(value: boolean): string {
return value ? 'Enabled' : 'Disabled';
}
/**
* Regenerates the engagement table from |info|.
*/
function renderTable() {
clearTable();
sortInfo();
assert(info);
assert(engagementTableBody);
info.filter(rowInfo => (showNoPlaybacks || rowInfo.mediaPlaybacks > 0))
.forEach(rowInfo => engagementTableBody!.appendChild(createRow(rowInfo)));
}
/**
* Retrieve media engagement info and render the engagement table.
*/
function updateEngagementTable() {
assert(detailsProvider);
// Populate engagement table.
detailsProvider.getMediaEngagementScoreDetails().then(response => {
info = response.info;
renderTable();
pageIsPopulatedResolver.resolve();
});
// Populate config settings.
detailsProvider.getMediaEngagementConfig().then(response => {
renderConfigTable(response.config);
});
}
document.addEventListener('DOMContentLoaded', function() {
detailsProvider = MediaEngagementScoreDetailsProvider.getRemote();
updateEngagementTable();
engagementTableBody =
document.querySelector<HTMLElement>('#engagement-table-body');
configTableBody = document.querySelector<HTMLElement>('#config-table-body');
// Set table header sort handlers.
const engagementTableHeader =
document.querySelector<HTMLElement>('#engagement-table-header');
assert(engagementTableHeader);
const headers = engagementTableHeader.children;
for (let i = 0; i < headers.length; i++) {
headers[i].addEventListener('click', (e) => {
const target = e.target as HTMLElement;
const newSortKey = target.getAttribute('sort-key');
if (sortKey === newSortKey) {
sortReverse = !sortReverse;
} else {
assert(newSortKey);
sortKey = newSortKey;
sortReverse = false;
}
const oldSortColumn = document.querySelector<HTMLElement>('.sort-column');
assert(oldSortColumn);
oldSortColumn.classList.remove('sort-column');
target.classList.add('sort-column');
if (sortReverse) {
target.setAttribute('sort-reverse', '');
} else {
target.removeAttribute('sort-reverse');
}
renderTable();
});
}
// Add handler to 'copy all to clipboard' button
const copyAllToClipboardButton =
document.querySelector<HTMLElement>('#copy-all-to-clipboard');
assert(copyAllToClipboardButton);
copyAllToClipboardButton.addEventListener('click', () => {
// Make sure nothing is selected
window.getSelection()!.removeAllRanges();
document.execCommand('selectAll');
document.execCommand('copy');
// And deselect everything at the end.
window.getSelection()!.removeAllRanges();
});
// Add handler to 'show no playbacks' checkbox
const showNoPlaybacksCheckbox =
document.querySelector<HTMLElement>('#show-no-playbacks');
assert(showNoPlaybacksCheckbox);
showNoPlaybacksCheckbox.addEventListener('change', (e) => {
showNoPlaybacks = (e.target as HTMLInputElement).checked;
renderTable();
});
});