2020/harness/util.js (428 lines of code) (raw):
/**
* @license
* Copyright 2018 Google Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
'use strict';
(function() {
const MEDIA_PATH = 'test-materials/media/';
const CERT_PATH = 'test-materials/cert/';
if (!Function.prototype.bind) {
Function.prototype.bind = function(oThis) {
if (typeof this !== 'function') {
throw new TypeError('What is trying to be bound is not a function');
}
var aArgs = Array.prototype.slice.call(arguments, 1);
var fToBind = this;
var fNOP = function() {};
var fBound = function() {
return fToBind.apply(
this instanceof fNOP && oThis ? this : oThis,
aArgs.concat(Array.prototype.slice.call(arguments)));
};
fNOP.prototype = this.prototype;
fBound.prototype = new fNOP();
return fBound;
};
}
var util = {};
// Begin non GitHub files
// Initiate backend to grab user token to access API.
util.getToken = function(onSuccess, interval) {
// Check if user clicks login again. Need to remove the last polling interval.
if (util.tokenInterval && interval!=util.tokenInterval) {
window.clearInterval(util.tokenInterval);
}
util.tokenInterval = interval;
var xhr = new XMLHttpRequest();
xhr.open("GET", "/token?branch=" + testVersion);
xhr.setRequestHeader("Content-type",
"application/x-www-form-urlencoded");
xhr.onreadystatechange = function() {
if (this.readyState === XMLHttpRequest.DONE){
if (this.status === 200) {
onSuccess();
window.clearInterval(interval);
} else if (this.status !== 428) {
// 428 means not ready, we can continue to call if it's not ready.
window.LOG(this, ["Login:", this.responseText]);
window.clearInterval(interval);
document.getElementById("login-pop-up").style.display = "none";
}
}
};
xhr.send();
};
// Gets the login code from backend.
util.login = function(onSuccess) {
var overlay = document.getElementById('login-pop-up');
if (overlay.style.display === 'inline-block')
{
// Disable this button if the overlay is already showing.
return;
}
overlay.style.display = 'inline-block';
var xhr = new XMLHttpRequest();
xhr.open("GET", "/login");
xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
xhr.onreadystatechange = function() {
if (this.readyState === XMLHttpRequest.DONE && this.status === 200) {
var response = JSON.parse(this.responseText);
document.getElementById("client-id").textContent =
`2) Enter this code\n${response.user_code}`;
var interval = window.setInterval(() => {
util.getToken(onSuccess, interval);
}, response.interval * 1050);
}
};
xhr.send();
};
// Uploads current test results to API
util.uploadTestResult = function(callBack) {
for (var test of window.globalRunner.testList) {
if (test && test.prototype.outcome!=0){
var xhr = new XMLHttpRequest();
xhr.open("POST", "/uploadTest?branch=" + testVersion);
xhr.setRequestHeader("Content-type",
"application/json");
var params = JSON.stringify({
test_results: [
{
test_case_id: test.prototype.id,
test_result: {
result: test.prototype.outcome
}
}
]
});
xhr.onreadystatechange = function() {
if (this.readyState === XMLHttpRequest.DONE) {
if (this.status != 200) {
window.LOG(this, ["upload error:", this.responseText]);
}
}
};
xhr.send(params);
}
}
callBack();
};
//End non GitHub files
util.createElement = function(tag, id, class_, innerHTML) {
var element = document.createElement(tag);
if (id != null)
element.id = id;
if (innerHTML != null)
element.innerHTML = innerHTML;
if (class_ != null)
element.classList.add(class_);
return element;
};
util.getClosestElement = function(refElement) {
if (arguments.length === 1)
return null;
var bestElement = arguments[1];
var bestDistance =
Math.abs((bestElement.offsetLeft + bestElement.offsetWidth / 2) -
(refElement.offsetLeft + refElement.offsetWidth / 2));
for (var i = 2; i < arguments.length; ++i) {
var currElement = arguments[i];
var currDistance =
Math.abs((currElement.offsetLeft + currElement.offsetWidth / 2) -
(refElement.offsetLeft + refElement.offsetWidth / 2));
if (currDistance < bestDistance) {
bestDistance = currDistance;
bestElement = currElement;
}
}
return bestElement;
};
util.fireEvent = function(obj, eventName) {
if (document.createEvent) {
var event = document.createEvent('MouseEvents');
event.initEvent(eventName, true, false);
obj.dispatchEvent(event);
} else if (document.createEventObject) {
obj.fireEvent('on' + eventName);
}
};
util.getElementWidth = function(element) {
var style = window.getComputedStyle(element);
var width = 0;
if (!isNaN(parseInt(style.width))) width += parseInt(style.width);
if (!isNaN(parseInt(style.marginLeft))) width += parseInt(style.marginLeft);
if (!isNaN(parseInt(style.marginRight))) width += parseInt(style.marginRight);
return width;
};
util.isValidArgument = function(arg) {
return typeof(arg) != 'undefined' && arg != null;
};
util.MakeCapitalName = function(name) {
name = name.substr(0, 1).toUpperCase() + name.substr(1);
var offset = 0;
for (;;) {
var space = name.indexOf(' ', offset);
if (space === -1)
break;
name = name.substr(0, space + 1) +
name.substr(space + 1, 1).toUpperCase() + name.substr(space + 2);
offset = space + 1;
}
return name;
};
util.MakeFieldName = function(name) {
var arr = name.split('-');
name = arr[0];
for (var i = 1; i < arr.length; i++) {
name += util.MakeCapitalName(arr[i]);
}
return name;
};
util.Round = function(value, digits) {
return Math.round(value * Math.pow(10, digits)) / Math.pow(10, digits);
};
util.ElapsedTimeInS = function() {
return Date.now() / 1000.0;
};
util.SizeToText = function(size, unitType) {
var unit = 'B';
if (!!unitType && (unitType == 'B' || unitType == 'b')) {
unit = unitType;
}
if (size >= 1024 * 1024) {
size /= 1024 * 1024;
unit = 'M';
} else if (size >= 1024) {
size /= 1024;
unit = 'K';
}
if ((size - Math.floor(size)) * 10 <
Math.floor(size))
size = Math.floor(size);
else
size = util.Round(size, 3);
return size + unit;
};
util.formatStatus = function(status) {
if (typeof status === 'undefined')
return 'undefined';
else if (typeof status === 'string')
return '"' + status + '"';
else if (typeof status === 'number')
return status.toString();
else if (typeof status === 'boolean')
return status ? 'true' : 'false';
throw 'unknown status type';
};
util.getAttr = function(obj, attr) {
attr = attr.split('.');
if (!obj || attr.length === 0)
return undefined;
while (attr.length) {
if (!obj)
return undefined;
obj = obj[attr.shift()];
}
return obj;
};
util.resize = function(str, newLength, fillValue) {
if (typeof str != 'string')
throw 'Only string is supported';
if (str.length > newLength) {
str.substr(0, newLength);
} else {
while (str.length < newLength)
str += fillValue;
}
return str;
};
util.stringToBoolean = function(str) {
if (typeof str === 'boolean') {
return str;
}
switch(str.toLowerCase().trim()) {
case 'false': case 'no': case 'off': case '0': case '': return false;
default: return true;
}
};
util.createUint8ArrayFromJSArray = function(arr) {
var uint8_arr = new Uint8Array(arr.length);
for (var i = 0; i < arr.length; i++) {
uint8_arr[i] = arr[i];
}
return uint8_arr;
};
var DLOG_LEVEL = 3;
// Log a debug message. Only logs if the given level is less than the current
// value of the global variable DLOG_LEVEL.
util.dlog = function(level) {
if (typeof(level) !== 'number')
throw 'level has to be an non-negative integer!';
// Comment this to prevent debug output
if (arguments.length > 1 && level <= DLOG_LEVEL) {
var args = [];
for (var i = 1; i < arguments.length; ++i)
args.push(arguments[i]);
if (window.LOG)
window.LOG.apply(null, args);
else
console.log(args);
}
};
/**
* Returns an AV1 codecs parameter string, e.g. "av01.0.05M.08"
* See https://aomediacodec.github.io/av1-isobmff/#codecsparam
*
* @param {?string} level from 2.0 to 7.3
* @param {?number} bitDepth 8 or 10
* @return {string}
*/
util.av1Codec = function(level = '2.0', bitDepth = 8) {
if (!/^[2-7]\.[0-3]$/.test(level)) {
throw `Invalid level: "${level}"`;
}
if (![8, 10].includes(bitDepth)) {
throw `Invalid bit depth: "${bitDepth}"`;
}
var zeroPad = n => {
// Returns a number (<100) formatted as a two-digit string.
return n < 10 ? `0${n}` : n.toString();
}
var fmtLevel = level => {
// Returns the value of seq_level_idx as defined in the spec.
var x_y = level.split('.');
var x = parseInt(x_y[0], 10);
var y = parseInt(x_y[1], 10);
var res = ((x - 2) * 4) + y;
return zeroPad(res);
};
var profile = 0; // Profile 0. No support for Profile 1 or Profile 2.
var tier = 'M'; // Main tier. No support yet for High tier.
return `av01.${profile}.${fmtLevel(level)}${tier}.${zeroPad(bitDepth)}`;
};
// return [width, height] of current window
util.getMaxWindow = function() {
return [
window.innerWidth * window.devicePixelRatio,
window.innerHeight * window.devicePixelRatio
];
};
util.getMaxVp9SupportedWindow = function() {
if (MediaSource.isTypeSupported(
'video/webm; codecs="vp9"; width=7680; height=4320;') &&
!MediaSource.isTypeSupported(
'video/webm; codecs="vp9"; width=9999; height=9999;')) {
return [7680, 4320];
} else if (MediaSource.isTypeSupported(
'video/webm; codecs="vp9"; width=3840; height=2160;') &&
!MediaSource.isTypeSupported(
'video/webm; codecs="vp9"; width=9999; height=9999;')) {
return [3840, 2160];
} else
return util.getMaxWindow();
};
util.getMaxH264SupportedWindow = function() {
if (MediaSource.isTypeSupported(
'video/mp4; codecs="avc1.4d401e"; width=1920; height=1080;') &&
!MediaSource.isTypeSupported(
'video/mp4; codecs="avc1.4d401e"; width=9999; height=9999;'))
return [1920, 1080];
else
return util.getMaxWindow();
};
util.getMaxAv1SupportedWindow = function() {
var checkSupport = spec => {
var type = [
'video/mp4',
`codecs="${util.av1Codec(spec.level)}"`,
`width=${spec.width}`,
`height=${spec.height}`,
].join('; ');
return MediaSource.isTypeSupported(type);
};
if (checkSupport({level: '2.0', width: 9999, height: 9999})) {
// Invalid resolution, default to window size
return util.getMaxWindow();
}
var specs = [
{level: '6.0', width: 7680, height: 4320},
{level: '5.0', width: 3840, height: 2160},
{level: '4.0', width: 1920, height: 1080},
];
for (var spec of specs) {
if (checkSupport(spec)) {
return [spec.width, spec.height];
}
}
// No valid resolutions, default to window size
return util.getMaxWindow();
};
util.is4k = function() {
return util.getMaxVp9SupportedWindow()[0] == 3840 &&
util.getMaxVp9SupportedWindow()[1] == 2160;
};
util.is8k = function() {
return util.getMaxVp9SupportedWindow()[0] == 7680 &&
util.getMaxVp9SupportedWindow()[1] == 4320;
};
util.isGtFHD = function() {
return ((util.getMaxWindow()[0] * util.getMaxWindow()[1]) > 2073600);
}
util.isGt4K = function() {
return ((util.getMaxWindow()[0] * util.getMaxWindow()[1]) > 8294400);
}
util.isCobalt = function() {
return navigator.userAgent.includes('Cobalt');
};
util.createUrlwithParams = function(url, params) {
return url + '?' + params.join('&');
};
util.createVideoFormatStr = function(
video, codec, width, height, framerate, spherical, suffix) {
return createMimeTypeStr(
'video/' + video, codec, width, height, framerate, spherical, suffix);
};
util.createSimpleVideoFormatStr = function(video, codec, suffix) {
return util.createVideoFormatStr(
video, codec, null, null, null, null, suffix);
};
util.createAudioFormatStr = function(audio, codec, suffix) {
return createMimeTypeStr(
'audio/' + audio, codec, null, null, null, null, suffix);
};
util.requireAV1 = function() {
if (util.isGt4K()) {
return true;
}
return util.isGtFHD() && util.supportHdr();
};
util.supportHdr = function() {
var supportEotf = eotf => {
return MediaSource.isTypeSupported(
util.createVideoFormatStr(
'webm', 'vp9.2', 1280, 720, 30, null, `eotf=${eotf}`));
};
if (supportEotf('strobevision')) {
// Invalid EOTF supported: MediaSource.isTypeSupported must be broken.
return false;
}
var eotfs = ['smpte2084', 'bt709', 'arib-std-b67'];
for (var i = 0; i < eotfs.length; i++) {
if (!supportEotf(eotfs[i])) {
return false;
}
}
return true;
};
util.supportWebSpeech = function() {
try {
// check if WebSpeech API is supported.
var recognition = new SpeechRecognition();
// check if the microphone itself is correctly connected.
navigator.mediaDevices.enumerateDevices().then(function(listOfDevices) {
for (var device of listOfDevices) {
if (device.kind == "audioinput") {
return true;
}
}
});
} catch (e) {}
return false;
};
util.compareResolutions = function(r1, r2) {
if (r1[r1.length - 1] != 'p' || r2[r2.length - 1] != 'p') {
throw "Resolution Format Error: should be {number}p"
}
var n1 = parseInt(r1);
var n2 = parseInt(r2);
if (isNaN(n1) || isNaN(n2) || n1 <= 0 || n2 <= 0) {
throw "Resolution Format Error: No valid number could be parsed."
}
if (n1 > n2) {
return 1;
} else if (n1 == n2) {
return 0;
} else {
return -1;
}
};
util.getMediaPath = function(filename) {
return MEDIA_PATH + filename;
};
util.getCertificatePath = function(filename) {
return CERT_PATH + filename;
};
window.util = util;
})();
try {
exports.util = window.util;
} catch (e) {
// do nothing, this function is not supposed to work for browser, but it's for
// Node js to generate json file instead.
}