var FunctionalTest = function()

in 2020/functional/functionalTest.js [24:1968]


var FunctionalTest = function() {

const URL_PATH = 'lib/functional/src/';
const MEDIA_PATH = 'test-materials/media/';
const MAX_URL_LENGTH = 2047;

const TEST_MEDIA_SRC = {
  Jpg: MEDIA_PATH + 'qual-e/pass.jpg',
  Png: MEDIA_PATH + 'qual-e/pass.png',
  Webp: MEDIA_PATH + 'qual-e/pass.webp',
  SslTestVideoMp4: MEDIA_PATH + 'qual-e/sslTestVideo.mp4',
  SslTestVideoWebm: MEDIA_PATH + 'qual-e/sslTestVideo.webm',
};

const MS_TO_WAIT_FOR_FONTS = 3000;
const COBALT_REQ_URL =
    'https://cobalt.googlesource.com/cobalt/+/20.lts.stable/' +
    'src/cobalt/build/build.id?format=TEXT';

var functionalVersion = 'Current Editor\'s Draft';
var webkitPrefix = MediaSource.prototype.version.indexOf('webkit') >= 0;
var tests = [];
var testArea = document.getElementById('testArea');

var info = 'No Media Support!';
if (window.MediaSource) {
  info = 'Spec Version: ' + functionalVersion;
  info += ' | webkit prefix: ' + webkitPrefix.toString();
}
info += ' | Default Timeout: ' + TestBase.timeout + 'ms';

var fields = ['passes', 'failures', 'timeouts'];

var createFunctionalTest =
    function(testId, name, category = 'Functional', mandatory = true) {
  var t = createTest(name, category, mandatory, testId, 'Functional Tests');
  t.prototype.index = tests.length;
  tests.push(t);
  return t;
};

// Create and check if the element is supported by browser
var createElement = function(elementName) {
  var element = document.createElement(elementName);
  if (Object.prototype.toString.call(element)
      === '[object HTMLUnknownElement]') {
    throw Error(elementName + ' not supported!');
  }
  return element;
};

/**
 * Validate the existence of AudioContext and its properties.
 */
var createAudioContextPropertyTest = function(testId, value, evaluate) {
  var name = value ? 'AudioContext.' + value : 'AudioContext';
  var test = createFunctionalTest(testId, name,'AudioContext');
  test.prototype.title = 'Test if' + name + ' exists';
  test.prototype.start = function(runner) {
    try {
      var audioCtx = new (window.AudioContext || window.webkitAudioContext)();
      if (!evaluate(audioCtx)) {
        throw 'No ' + name + '.';
      }
      runner.succeed();
    } catch (e) {
      runner.fail(e);
    } finally {
      audioCtx.close();
    }
  }
};

createAudioContextPropertyTest('14.1.1.1', null, function(audioCtx) {
  return !!(audioCtx);
});
createAudioContextPropertyTest('14.1.2.1', 'destination', function(audioCtx) {
  return !!(audioCtx.destination);
});
createAudioContextPropertyTest('14.1.3.1', 'sampleRate', function(audioCtx) {
  return typeof audioCtx.sampleRate === 'number';
});
createAudioContextPropertyTest('14.1.4.1', 'currentTime', function(audioCtx) {
  return isNaN(audioCtx.currentTime) == false;
});
createAudioContextPropertyTest(
    '14.1.5.1', 'decodeAudioData', function(audioCtx) {
      return !!(audioCtx.decodeAudioData);
    });
createAudioContextPropertyTest(
  '14.1.6.1', 'createBufferSource', function(audioCtx) {
    return (
        !!audioCtx.createBufferSource &&
        audioCtx.createBufferSource() instanceof AudioBufferSourceNode);
  });

/**
 * Validate the existence of AudioBufferSourceNode and its properties.
 */
var createAudioBufferSourceNodeTest = function(
    testId, value, evaluate, mandatory) {
  var name = value ? 'AudioBufferSourceNode.' + value : 'AudioBufferSourceNode';
  var test =
      createFunctionalTest(testId, name, 'AudioBufferSourceNode', mandatory);
  test.prototype.title = 'Test if ' + name + ' exists.';
  test.prototype.start = function(runner) {
    try {
      var audioCtx = new (window.AudioContext || window.webkitAudioContext)();
      var source = audioCtx.createBufferSource();
      if (!evaluate(source)) {
        throw 'No ' + name + '.';
      }
      runner.succeed();
    } catch (e) {
      return runner.fail(e);
    } finally {
      audioCtx.close();
    }
  }
};

createAudioBufferSourceNodeTest('14.2.1.1', null, function(source) {
  return !!source;
});
createAudioBufferSourceNodeTest('14.2.2.1', 'buffer', function(source) {
  return !!(typeof source.buffer);
});
createAudioBufferSourceNodeTest('14.2.3.1', 'onended', function(source) {
  return !!(typeof source.onended);
});
createAudioBufferSourceNodeTest(
    '14.2.4.1',
    'playbackRate',
    function(source) {
      return (typeof source.playbackRate.defaultValue === 'number') &&
          (typeof source.playbackRate.value === 'number');
    },
    false);
createAudioBufferSourceNodeTest('14.2.5.1', 'start', function(source) {
  return !!(typeof source.start);
});
createAudioBufferSourceNodeTest('14.2.6.1', 'stop', function(source) {
  return !!(typeof source.stop);
});

/**
 * Validate the existence of AudioNode and its properties.
 */
var createAudioNodeTest = function(testId, value, evaluate) {
  var name = value ? 'AudioNode.' + value : 'AudioNode';
  var test = createFunctionalTest(testId, name, 'AudioNode');
  test.prototype.title = 'Test if ' + name + ' exists.';
  test.prototype.start = function(runner) {
    try {
      var audioCtx = new (window.AudioContext || window.webkitAudioContext)();
      var node = audioCtx.destination;
      if (!evaluate(node, audioCtx)) {
        throw 'No ' + name + '.';
      }
      runner.succeed();
    } catch (e) {
      runner.fail(e);
    } finally {
      audioCtx.close();
    }
  }
};

createAudioNodeTest('14.3.1.1', null, function(node) {
  return !!(node);
});
createAudioNodeTest('14.3.2.1', 'context', function(node, audioCtx) {
  return node.context === audioCtx;
});
createAudioNodeTest('14.3.3.1', 'numberOfInputs', function(node) {
  return typeof node.numberOfInputs === 'number';
});
createAudioNodeTest('14.3.4.1', 'numberOfOutputs', function(node) {
  return typeof node.numberOfOutputs === 'number';
});
createAudioNodeTest('14.3.5.1', 'channelCountMode', function(node) {
  return typeof node.channelCountMode === 'string';
});
createAudioNodeTest('14.3.6.1', 'channelInterpretation', function(node) {
  return typeof node.channelInterpretation === 'string';
});
createAudioNodeTest('14.3.7.1', 'connect', function(node, audioCtx) {
  return (!!(audioCtx.createBufferSource().connect));
});
createAudioNodeTest('14.3.8.1', 'disconnect', function(node, audioCtx) {
  return (!!(audioCtx.createBufferSource().disconnect));
});


// expand url into MAX length
var expandUrl = function(url, charset) {
  return url + new Array(MAX_URL_LENGTH - url.length - 1).join(charset);
}

/**
 * Ensure super long navigation URL is properly supported.
 */
var testNavigationURLLength = createFunctionalTest(
    '14.4.1.1', 'NavigationURLLength', 'URL Length', false);
testNavigationURLLength.prototype.title = 'Test Navigation URL Length support.';
testNavigationURLLength.prototype.start = function(runner) {
  try {
    var navElement = createElement('iframe');
    var randChar =
        'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
            .charAt(Math.floor(Math.random() * 62));
    var url = expandUrl(URL_PATH + 'navUrlTest.html?q=', randChar);
    navElement.setAttribute('src', url);
    navElement.setAttribute('id', 'urlTestFrame' + randChar);
    testArea.appendChild(navElement);
    var frame = document.getElementById('urlTestFrame' + randChar);
    frame.onload = function() {
      var doc = frame.contentDocument ||
          frame.contentWindow.document || frame.document;
      var result = doc.getElementsByTagName('title')[0].innerHTML;
      this.log('Result: ' + result)
      testArea.removeChild(navElement);
      if (result == 'Success') {
        return runner.succeed();
      }
      throw Error(result);
    }.bind(this);
  } catch (e) {
    return runner.fail(e);
  }
};

/**
 * Ensure super long image src URL is properly supported.
 */
var testImageSrcURLLength = createFunctionalTest(
    '14.4.2.1', 'ImageSrcURLLength', 'URL Length');
testImageSrcURLLength.prototype.title = 'Test Image Src URL Length support.';
testImageSrcURLLength.prototype.start = function(runner) {
  try {
    var imgElement = createElement('img');
    var url = expandUrl(TEST_MEDIA_SRC.Jpg + '?q=', 'a');
    imgElement.setAttribute('src', url);
    testArea.appendChild(imgElement);
    imgElement.onload = function() {
      testArea.removeChild(imgElement);
      imgElement = null;
      return runner.succeed();
    }.bind(this);
  } catch (e) {
    return runner.fail(e);
  }
};

/**
 * Ensure super long video src URL is properly supported.
 */
var createVideoSrcURLTest = function(testId, format, src) {
  var test = createFunctionalTest(
      testId, 'VideoSrcURLLength - ' + format, 'URL Length');
  test.prototype.title = 'Test ' + format + ' Video Src URL Length support.';
  test.prototype.start = function(runner, video) {
    try {
      var randChar =
          'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
              .charAt(Math.floor(Math.random() * 62));
      var longUrl = expandUrl(src + '?q=', randChar);
      video.setAttribute('src', longUrl);
      video.onplaying = function onPlaying() {
        video.removeEventListener('onplaying', onPlaying);
        return runner.succeed();
      }.bind(this);
      video.onerror = function() {
        var message = String().concat(this.name + ' - value returned: ', false);
        throw Error(message);
      }.bind(this);
      video.play();
    } catch (e) {
      return runner.fail(e);
    }
  }
};

createVideoSrcURLTest('14.4.3.1', 'mp4', TEST_MEDIA_SRC.SslTestVideoMp4);


/**
 * Ensure super long XHR request URL is properly supported.
 */
var testXHRURLLength =
    createFunctionalTest('14.4.4.1', 'XHRURLLength', 'URL Length');
testXHRURLLength.prototype.title = 'Test XHR Src URL Length support.';
testXHRURLLength.prototype.start = function(runner) {
  try {
    var xmlHttp = new XMLHttpRequest();
    var url = expandUrl(TEST_MEDIA_SRC.Png + '?q=', 'a');
    xmlHttp.open('GET', url, true);
    xmlHttp.send(null);
    xmlHttp.onreadystatechange = function() {
      if (xmlHttp.readyState == 4) {
        if (xmlHttp.status == 200) {
          return runner.succeed();
        } else {
          runner.fail(Error('Xhr request status is not 200 OK'));
        }
      }
    };
  } catch (e) {
    return runner.fail(e);
  }
};

/**
 * Validate the existence of CORS through Modernizr.
 */
var testCORS = createFunctionalTest('14.5.1.1', 'CORS', 'Security');
testCORS.prototype.title = 'Test if CORS works.';
testCORS.prototype.start = function(runner) {
  try {
    if (window.Modernizr.cors) {
      return runner.succeed();
    }
    throw Error('CORS is not supported.');
  } catch (e) {
    return runner.fail(e);
  }
};

/**
 * Validate the existence of localStorage properties.
 */
var createLocalStorageTest = function(testId, value, evaluate, cleanup) {
  var name = value ? 'localStorage.' + value : 'localStorage';
  var test = createFunctionalTest(testId, name, 'localStorage');
  test.prototype.title = 'Test if ' + name + ' works.';
  test.prototype.start = function(runner) {
    try {
      if (evaluate()) {
        return runner.succeed();
      }
      throw name + ' errors';
    } catch (e) {
      if (cleanup) {
        cleanup();
      }
      return runner.fail(e);

    }
  }
};

createLocalStorageTest('14.6.1.1', null, function() {
  return Modernizr.localstorage;
});
createLocalStorageTest('14.6.2.1', 'setItem', function() {
  return Modernizr.prefixed('setItem', window.localStorage , false) != false;
});
createLocalStorageTest('14.6.3.1', 'getItem', function() {
  return Modernizr.prefixed('getItem', window.localStorage , false) != false;
});
createLocalStorageTest('14.6.4.1', 'removeItem', function() {
  return Modernizr.prefixed('removeItem', window.localStorage , false) != false;
});
createLocalStorageTest(
    '14.6.5.1',
    '1M Char Quota',
    function() {
      var testData = new Array(1000 * 1000).join('2');
      localStorage.setItem('yt-quale', testData);
      localStorage.removeItem('yt-quale');
      return true;
    },
    function() {
      localStorage.removeItem('yt-quale');
    });

/**
 * Test if HTTPS onload fires.
 */
var testHTTPS = createFunctionalTest('14.7.1.1', 'HTTPS', 'HTTP');
testHTTPS.prototype.title = 'Test if HTTPS onload fires.';
testHTTPS.prototype.start = function(runner) {
  try {
      var sslImg = createElement('img');
      sslImg.setAttribute(
          'src', 'https://www.google.com/images/srpr/logo3w.png');
      testArea.appendChild(sslImg);
      sslImg.onload  = function() {
        testArea.removeChild(sslImg);
        sslImg = null;
        return runner.succeed();
      }.bind(this);
  } catch (e) {
    return runner.fail(e);

  }
};

/**
 * Test if XMLHTTPRequest works.
 */
var testXMLHTTPRequest =
    createFunctionalTest('14.7.2.1', 'XMLHTTPRequest', 'HTTP');
testXMLHTTPRequest.prototype.title = 'Test if XMLHTTPRequest works.';
testXMLHTTPRequest.prototype.start = function(runner) {
  try {
    if (new XMLHttpRequest()) {
      return runner.succeed();
    }
  } catch (e) {
    return runner.fail(e);
  }
};

/**
 * Test if XMLHTTPRequest Level 2 works.
 */
var testXMLHTTPRequestLevel2 =
    createFunctionalTest('14.7.3.1', 'XMLHTTPRequest Level 2', 'HTTP');
testXMLHTTPRequestLevel2.prototype.title =
    'Test if XMLHTTPRequest Level 2 works.';
testXMLHTTPRequestLevel2.prototype.start = function(runner) {
  try {
    // test from https://github.com/NielsLeenheer/html5test/pull/100
    if (window.XMLHttpRequest && ('upload' in new XMLHttpRequest())) {
      return runner.succeed();
    }
    throw Error('Does not support XMLHTTPRequest Level 2');
  } catch (e) {
    return runner.fail(e);
  }
};

/**
 * Validate Fetch API properties work as expect.
 */
var createFetchAPITest = function(testId, value, evaluate) {
  var name = 'Fetch API - ' + value;
  var test = createFunctionalTest(testId, name, 'Fetch API');
  test.prototype.title = 'Test if ' + value + ' in Fetch API works.';
  test.prototype.start = function(runner) {
    try {
      if (evaluate()) {
        return runner.succeed();
      }
      throw Error(name + ' errors');
    } catch (e) {
      return runner.fail(e);
    }
  }
};

createFetchAPITest('14.8.1.1', 'fetch()', function() {
  return !!(window.fetch);
});
createFetchAPITest('14.8.2.1', 'Headers', function() {
  var httpHeaders = {
    'Content-Type' : 'image/jpeg',
    'Accept-Charset' : 'utf-8'
  };
  var testHeaders = new Headers(httpHeaders);
  return testHeaders.get('Content-Type') == "image/jpeg";
});

/**
 * Validate Fetch API Request work as expect.
 */
var testFetchAPIRequest =
    createFunctionalTest('14.8.3.1', 'Fetch API - Request', 'Fetch API');
testFetchAPIRequest.prototype.title = 'Test if Fetch API Request works.';
testFetchAPIRequest.prototype.start = function(runner) {
  try {
    var fetchRequest = new Request(
        'https://qual-e.appspot.com/test',
        {method: 'POST', body: '{"result":"pass"}'});
    // This shouldn't work, as method is read-only
    fetchRequest.method = 'GET';
  } catch (e) {
    if (fetchRequest.url == 'https://qual-e.appspot.com/test' &&
        fetchRequest.method == 'POST' && fetchRequest.credentials != 'include')
      return runner.succeed();
  }
  return runner.fail('Fetch API Request should be read-only.')
};

/**
 * Validate Fetch API Response work as expect.
 */
var testFetchAPIResponse =
    createFunctionalTest('14.8.4.1', 'Fetch API - Response', 'Fetch API');
testFetchAPIResponse.prototype.title = 'Test if Fetch API Response works.';
testFetchAPIResponse.prototype.start = function(runner) {
  try {
    var init = {'status': 200, 'statusText': 'YouTube'};
    var myResponse = new Response('test_body', init);
    myResponse.statusText = 'Should not work since it is read-only';
  } catch (e) {
    if (myResponse.statusText == 'YouTube' && myResponse.ok) {
      return runner.succeed();
    }
  }
  return runner.fail('Fetch API does not work as expected');
};

/**
 * Test fetch API's streaming feature.
 */
var testFetchStreamAPIRequest =
    createFunctionalTest('14.8.5.1', 'Fetch API - stream', 'Fetch API');
testFetchStreamAPIRequest.prototype.title =
    'Test if Fetch API handles steaming. ';
testFetchStreamAPIRequest.prototype.start = function(runner){
  try {
    const FETCH_TIME_OUT = 3000;
    const timeout = setTimeout(function(){
      throw Error('Fetch took too long');
    }, FETCH_TIME_OUT);
    fetch('lib/functional/fetch-sample.txt').then(function(response) {
      var reader = response.body.getReader();
      var partialCell = '';
      var returnNextCell = false;
      var returnCellAfter = "YouTube";
      function search() {
        return reader.read().then(function(result) {
          var chunk = result.value;
          for (var i = 3; i < chunk.byteLength; i++) {
            partialCell += String.fromCharCode(chunk[i]);
          }
          var cellBoundry = /(?:,|\r\n)/;
          var completeCells = partialCell.split(cellBoundry);
          if (!result.done) {
            // last cell may not be complete
            partialCell = completeCells[completeCells.length - 1];
            completeCells = completeCells.slice(0, -1);
          }
          for (var cell of completeCells) {
            cell = cell.trim();
            if (returnNextCell) {
              reader.cancel("No more reading needed.");
              return cell;
            }
            if (cell === returnCellAfter) {
              returnNextCell = true;
            }
          }
          if (result.done) {
            throw Error("Could not find value after " + returnCellAfter);
          }
          return search();
        });
      }
      return search();
    }).then(function(){
      clearTimeout(timeout);
      return runner.succeed();
    });
  } catch (e) {
    return runner.fail(e);
  }
};

/**
 * Validate Streams API - ReadableByteStream.
 */
var testStreamsAPI = createFunctionalTest(
    '14.9.1.1', 'Streams API - ReadableByteStream', 'Assorted');
testStreamsAPI.prototype.title = 'Test Streams API - ReadableByteStream.';
testStreamsAPI.prototype.start = function(runner) {
  try {
    var init = {'status': 200, 'statusText' : 'YouTube'};
    var myResponse = new Response('test_body', init);
    if (!!window.ReadableStream && myResponse.ok) {
      return runner.succeed();
    }
    throw Error('Response not ok');
  } catch (e) {
    return runner.fail(e);
  }
};

/**
 * Validate browser supports strict mode in ECMA262-5.
 */
var testECMA262 =
    createFunctionalTest('14.9.2.1', 'ECMA262-5 Strict Mode', 'Assorted');
testECMA262.prototype.title = 'Test browser supports strict mode in ECMA262-5.';
testECMA262.prototype.start = function(runner) {
  try {
    'use strict';
    var a = 1;
    return runner.succeed();
  } catch (e) {
    return runner.fail(e)
  }
};

/**
 * Validate the existence of window.requestAnimationFrame through Modernizr.
 */
var testAnimationFrame =
    createFunctionalTest('14.9.3.1', 'RequestAnimationFrame', 'Assorted');
testAnimationFrame.prototype.title = 'Test request Animation Frame.';
testAnimationFrame.prototype.start = function(runner) {
  try {
    if (Modernizr.prefixed('requestAnimationFrame', window, false)) {
      return runner.succeed();
    }
    throw Error('window.requestAnimationFrame does not exist');
  } catch (e) {
    return runner.fail(e)
  }
};

/**
 * Test if js date object is correctly supported.
 */
var testJSDateObject =
    createFunctionalTest('14.9.4.1', 'JavaScript Date Object', 'Assorted');
testJSDateObject.prototype.title = 'Test if js date object is supported.';
testJSDateObject.prototype.start = function(runner) {
  try {
    var dateObj = new Date(Date.parse('2012-11-01T14:12:09.000Z'));
    var dateObjFormatted = dateObj.getUTCDate() + '/' +
        (dateObj.getUTCMonth() + 1) + '/' +
        dateObj.getUTCFullYear().toString().substr(2,2);
    if (dateObjFormatted == '1/11/12' || dateObjFormatted == '2012/11/2') {
      return runner.succeed();
    }
    throw Error('date object is not supported correctly');
  } catch (e) {
    return runner.fail(e)
  }
};

/**
 * Validate support for specified image format.
 */
var createImgTest = function(testId, suffix) {
  var test = createFunctionalTest(testId, suffix.toUpperCase(), 'Assorted');
  test.prototype.title = 'Test image format ' + suffix;
  test.prototype.start = function(runner) {
    try {
      var imgElement = createElement('img');
      var url = TEST_MEDIA_SRC[suffix];
      imgElement.setAttribute('src', url);
      testArea.appendChild(imgElement);
      imgElement.onload = function() {
        testArea.removeChild(imgElement);
        imgElement = null;
        return runner.succeed();
      }.bind(this);
    } catch (e) {
        return runner.fail(e);
    }
  }
};

createImgTest('14.9.5.1', 'Jpg');
createImgTest('14.9.6.1', 'Png');

/**
 * Validate the window height and width are in right size.
 */
var testWindowSize =
    createFunctionalTest('14.9.7.1', 'Window Size', 'Assorted');
testWindowSize.prototype.title = 'Test window size in current browser.';
testWindowSize.prototype.start = function(runner) {
  try {
    var result = false;
    switch (window.innerHeight) {
      case 720:
        if(window.innerWidth == 1280)
          result = true;
        break;
      case 1080:
        if(window.innerWidth == 1920)
          result = true;
        break;
      case 1440:
        if(window.innerWidth == 2560)
          result = true;
        break;
      case 2160:
        if(window.innerWidth == 3840)
          result = true;
        break;
    }
    if (!result) {
      throw 'Failed test with window size in ' +
      window.innerWidth + ' x ' + window.innerHeight;
    }
    return runner.succeed();
  } catch (e) {
    return runner.fail(e);
  }
};

/**
 * Validate existence of WebP properties.
 */
var createWebPTest = function(testId, name, uri) {
  var test = createFunctionalTest(testId, name, 'WebP');
  test.prototype.title = 'Test ' + name + ' support.';
  test.prototype.start = function(runner) {
    var img = new Image();

    img.addEventListener('load', function() {
      return runner.succeed();
    }, false);
    img.addEventListener('error', function(event) {
      return runner.fail('Request to website with valid SSL certificate fails.');
    }, false);

    img.src = uri;
  };
};

var webpTests = [{
  'uri': 'data:image/webp;base64,UklGRiQAAABXRUJQVlA4IBgAAAAwAQCdASoBAAEAAwA0JaQAA3AA/vuUAAA=',
  'name': 'WebP'
}, {
  'uri': 'data:image/webp;base64,UklGRkoAAABXRUJQVlA4WAoAAAAQAAAAAAAAAAAAQUxQSAwAAAABBxAR/Q9ERP8DAABWUDggGAAAADABAJ0BKgEAAQADADQlpAADcAD++/1QAA==',
  'name': 'WebP Alpha'
}, {
  'uri': 'data:image/webp;base64,UklGRlIAAABXRUJQVlA4WAoAAAASAAAAAAAAAAAAQU5JTQYAAAD/////AABBTk1GJgAAAAAAAAAAAAAAAAAAAGQAAABWUDhMDQAAAC8AAAAQBxAREYiI/gcA',
  'name': 'WebP Animation'
}, {
  'uri': 'data:image/webp;base64,UklGRh4AAABXRUJQVlA4TBEAAAAvAAAAAAfQ//73v/+BiOh/AAA=',
  'name': 'WebP Lossless'
}];

for (var i = 0; i < webpTests.length; i++) {
  createWebPTest(`14.10.${i + 1}.1`, webpTests[i].name, webpTests[i].uri);
}

/**
 * Test WebSpeech API through Modernizr.
 */
var testWebSpeechAPI = createFunctionalTest(
    '14.12.1.1', 'WebSpeech API', 'Assorted', harnessConfig.support_webspeech);
testWebSpeechAPI.prototype.title = 'Test WebSpeech API.';
testWebSpeechAPI.prototype.start = function(runner) {
  try {
    if (Modernizr.speechrecognition || Modernizr.mediarecorder) {
      return runner.succeed();
    }
    throw 'Neither speechrecognition nor mediarecorder supported.';
  } catch (e) {
    return runner.fail(e);
  }
};

/**
 * Ensure video/x-flv is unsupported.
 */
var testNoVideoXFlv = createFunctionalTest(
    '14.12.2.1', 'video/x-flv is unsupported', 'Assorted');
testNoVideoXFlv.prototype.title = 'Make sure video/x-flv is unsupported.';
testNoVideoXFlv.prototype.start = function(runner) {
  try {
    if (!Modernizr.video.flv) {
      return runner.succeed();
    }
    throw 'video/x-flv is still supported.'
  } catch (e) {
    return runner.fail(e);
  }
};

/**
 * Validate MediaSource properties through Modernizr.
 */
var createMediaSourceTest = function(testId, arg) {
  var test = createFunctionalTest(testId, 'MediaSource.' + arg, 'MediaSource');
  test.prototype.title = 'Test if MediaSource.' + arg + ' is supported.';
  test.prototype.start = function(runner, video) {
    try {
      var media = new MediaSource();
      window.attachMediaSource(video, media);
      if (Modernizr.prefixed(arg, media, false) != false) {
        return runner.succeed();
      }
      throw 'MediaSource.' + arg + ' support failed.';
    } catch (e) {
      return runner.fail(e);
    }
  };
}

createMediaSourceTest('14.13.1.1', 'addSourceBuffer');
createMediaSourceTest('14.13.2.1', 'sourceBuffers');
createMediaSourceTest('14.13.3.1', 'activeSourceBuffers');
createMediaSourceTest('14.13.4.1', 'duration');
createMediaSourceTest('14.13.5.1', 'removeSourceBuffer');
createMediaSourceTest('14.13.6.1', 'readyState');
createMediaSourceTest('14.13.7.1', 'endOfStream');

/**
 * Test if specified type is supported through MediaSource.isTypeSupported.
 */
var createSupportTest = function(testId, name, evaluate, mandatory) {
  var logResult = function(format, result) {
    this.runner.log('[' + this.desc + '] - isTypeSupported("' + format +
        '") returned: ' + result);
  };

  var test = createFunctionalTest(testId, name, 'Support', mandatory);
  test.prototype.title = 'Test ' + name;
  test.prototype.start = function(runner) {
    try {
      if (evaluate(logResult.bind(this))) {
        return runner.succeed();
      }
      throw name + ' failed.';
    } catch (e) {
      return runner.fail(e);
    }
  }
};


createSupportTest(
    '14.14.1.1', 'isTypeSupported cryptoblockformat', function(logResult) {
  var invalidCryptoBlockFormatType = util.createVideoFormatStr(
      'webm', 'vp9', 1280, 720, 23.976, null, 'cryptoblockformat=invalid');
  var invalidCryptoBlockFormatSupported =
      MediaSource.isTypeSupported(invalidCryptoBlockFormatType);
  logResult(
      invalidCryptoBlockFormatType, invalidCryptoBlockFormatSupported);

  var validCryptoBlockFormatType = util.createVideoFormatStr(
      'webm', 'vp9', 1280, 720, 23.976, null, 'cryptoblockformat=subsample');
  var validCryptoBlockFormatSupported =
      MediaSource.isTypeSupported(validCryptoBlockFormatType);
  logResult(validCryptoBlockFormatType, validCryptoBlockFormatSupported);

  if (!invalidCryptoBlockFormatSupported && validCryptoBlockFormatSupported) {
    return true;
  }
});


var isTypeSupportedTest = function(logResult) {
  var baselineVideoType = util.createVideoFormatStr(
      'mp4', 'avc1.4d401e', 640, 360, 30, null, 'bitrate=300000');
  var baselineVideoSupported =
      MediaSource.isTypeSupported(baselineVideoType);
  logResult(baselineVideoType, baselineVideoSupported);

  var baselineAudioType =
      util.createAudioFormatStr('mp4', 'mp4a.40.2', 'channels=2');
  var baselineAudioSupported = MediaSource.isTypeSupported(baselineAudioType);
  logResult(baselineAudioType, baselineAudioSupported);

  var invalidVideoHeightType = util.createVideoFormatStr(
      'mp4', 'avc1.4d401e', 640, 10360);
  var invalidVideoHeightSupported =
      MediaSource.isTypeSupported(invalidVideoHeightType);
  logResult(invalidVideoHeightType, invalidVideoHeightSupported);

  var invalidAudioChannelsType = util.createAudioFormatStr(
      'mp4', 'mp4a.40.2', 'channels=100');
  var invalidAudioChannelsSupported =
      MediaSource.isTypeSupported(invalidAudioChannelsType);
  logResult(invalidAudioChannelsType, invalidAudioChannelsSupported);

  if (baselineVideoSupported && baselineAudioSupported &&
      !invalidVideoHeightSupported && !invalidAudioChannelsSupported) {
    return true;
  }
};

createSupportTest(
    '14.14.2.1', 'isTypeSupported Extensions', isTypeSupportedTest);

createSupportTest(
    '14.14.3.1',
    'H.264 60fps Support',
    function(logResult) {
      if (!(isTypeSupportedTest(logResult))) return false;

      var maxWidth = util.getMaxH264SupportedWindow()[0];
      var maxHeight = util.getMaxH264SupportedWindow()[1];

      var invalidH264Type = util.createVideoFormatStr(
          'mp4', 'avc1.4d401e', 640, 360, 9999);
      var invalidH264Supported = MediaSource.isTypeSupported(invalidH264Type);
      logResult(invalidH264Type, invalidH264Supported);

      var baselineH264Type = util.createVideoFormatStr(
          'mp4', 'avc1.4d401e', 640, 360, 60)
      var baselineH264Supported = MediaSource.isTypeSupported(baselineH264Type);
      logResult(baselineH264Type, baselineH264Supported);

      var maxResolutionH264Type = util.createVideoFormatStr(
          'mp4', 'avc1.4d401e', maxWidth, maxHeight, 60);
      var maxResolutionH264Supported =
          MediaSource.isTypeSupported(maxResolutionH264Type);
      logResult(maxResolutionH264Type, maxResolutionH264Supported);

      if (baselineH264Supported && maxResolutionH264Supported &&
          !invalidH264Supported) {
        return true;
      }
    });

createSupportTest(
    '14.14.4.1',
    'VP9 60fps Support',
    function(logResult) {
      if (!(isTypeSupportedTest(logResult))) return false;

      var maxWidth = util.getMaxVp9SupportedWindow()[0];
      var maxHeight = util.getMaxVp9SupportedWindow()[1];

      var invalidVp9Type =
          util.createVideoFormatStr('webm', 'vp9', 640, 360, 9999);
      var invalidVp9Supported = MediaSource.isTypeSupported(invalidVp9Type);
      logResult(invalidVp9Type, invalidVp9Supported);

      var baselineVp9Type =
          util.createVideoFormatStr('webm', 'vp9', 640, 360, 60);
      var baselineVp9Supported = MediaSource.isTypeSupported(baselineVp9Type);
      logResult(baselineVp9Type, baselineVp9Supported);

      var maxVp9Type =
          util.createVideoFormatStr('webm', 'vp9', maxWidth, maxHeight, 60);
      var maxVp9Supported = MediaSource.isTypeSupported(maxVp9Type);
      logResult(maxVp9Type, maxVp9Supported);

      if (baselineVp9Supported && maxVp9Supported && !invalidVp9Supported) {
        return true;
      }
    });

createSupportTest(
    '14.14.5.1',
    'H.264 120fps Support',
    function(logResult) {
      if (!(isTypeSupportedTest(logResult))) return false;

      var maxWidth = util.getMaxH264SupportedWindow()[0];
      var maxHeight = util.getMaxH264SupportedWindow()[1];

      var invalidH264Type = util.createVideoFormatStr(
          'mp4', 'avc1.4d401e', 640, 360, 9999);
      var invalidH264Supported = MediaSource.isTypeSupported(invalidH264Type);
      logResult(invalidH264Type, invalidH264Supported);

      var baselineH264Type = util.createVideoFormatStr(
          'mp4', 'avc1.4d401e', 640, 360, 120);
      var baselineH264Supported = MediaSource.isTypeSupported(baselineH264Type);
      logResult(baselineH264Type, baselineH264Supported);

      var maxResolutionH264Type = util.createVideoFormatStr(
          'mp4', 'avc1.4d401e', maxWidth, maxHeight, 120);;
      var maxResolutionH264Supported =
          MediaSource.isTypeSupported(maxResolutionH264Type);
      logResult(maxResolutionH264Type, maxResolutionH264Supported);

      if (baselineH264Supported && maxResolutionH264Supported &&
          !invalidH264Supported) {
        return true;
      }
    },
    false);

createSupportTest(
    '14.14.6.1',
    'VP9 120fps Support',
    function(logResult) {
      if (!(isTypeSupportedTest(logResult))) return false;

      var maxWidth = util.getMaxVp9SupportedWindow()[0];
      var maxHeight = util.getMaxVp9SupportedWindow()[1];

      var invalidVp9Type =
          util.createVideoFormatStr('webm', 'vp9', 640, 360, 9999);
      var invalidVp9Supported = MediaSource.isTypeSupported(invalidVp9Type);
      logResult(invalidVp9Type, invalidVp9Supported);

      var baselineVp9Type =
          util.createVideoFormatStr('webm', 'vp9', 640, 360, 120);
      var baselineVp9Supported = MediaSource.isTypeSupported(baselineVp9Type);
      logResult(baselineVp9Type, baselineVp9Supported);

      var maxVp9Type = util.createVideoFormatStr(
          'webm', 'vp9', maxWidth, maxHeight, 120);
      var maxVp9Supported = MediaSource.isTypeSupported(maxVp9Type);
      logResult(maxVp9Type, maxVp9Supported);

      if (baselineVp9Supported && maxVp9Supported && !invalidVp9Supported) {
        return true;
      }
    },
    false);

createSupportTest(
    '14.14.7.1',
    'isTypeSupported EOTF Support',
    function(logResult) {
      var smpte2084Type = util.createVideoFormatStr(
          'webm', 'vp9.2', 1280, 720, 30, null, 'eotf=smpte2084');
      var smpte2084Supported = MediaSource.isTypeSupported(smpte2084Type);
      logResult(smpte2084Type, smpte2084Supported);

      var bt709Type = util.createVideoFormatStr(
          'webm', 'vp9.2', 1280, 720, 30, null, 'eotf=bt709');
      var bt709Supported = MediaSource.isTypeSupported(bt709Type);
      logResult(bt709Type, bt709Supported);

      var hlgType = util.createVideoFormatStr(
          'webm', 'vp9.2', 1280, 720, 30, null, 'eotf=arib-std-b67');
      var hlgSupported = MediaSource.isTypeSupported(hlgType);
      logResult(hlgType, hlgSupported);

      var invalidEOTFType = util.createVideoFormatStr(
          'webm', 'vp9.2', 1280, 720, 30, null, 'eotf=strobevision');
      var invalidEOTFSupported = MediaSource.isTypeSupported(invalidEOTFType);
      logResult(invalidEOTFType, invalidEOTFSupported);

      if (smpte2084Supported && bt709Supported &&
          hlgSupported && !invalidEOTFSupported) {
        return true;
      }
    },
    harnessConfig.support_hdr);

createSupportTest(
    '14.14.8.1',
    'SMPTE2084 Support',
    function(logResult) {
      var smpte2084Type =
          util.createSimpleVideoFormatStr('webm', 'vp9.2', 'eotf=smpte2084');
      var smpte2084Supported = MediaSource.isTypeSupported(smpte2084Type);
      logResult(smpte2084Type, smpte2084Supported);

      if (smpte2084Supported) {
        return true;
      }
    },
    harnessConfig.support_hdr);

createSupportTest(
    '14.14.9.1',
    'ARIB STD-B67 Support',
    function(logResult) {
      var hlgType =
          util.createSimpleVideoFormatStr('webm', 'vp9.2', 'eotf=arib-std-b67');
      var hlgSupported = MediaSource.isTypeSupported(hlgType);
      logResult(hlgType, hlgSupported);

      if (hlgSupported) {
        return true;
      }
    },
    harnessConfig.support_hdr);

createSupportTest(
    '14.14.10.1',
    'ITU-R BT.709 Support',
    function(logResult) {
      var bt709Type =
          util.createSimpleVideoFormatStr('webm', 'vp9.2', 'eotf=bt709');
      var bt709Supported = MediaSource.isTypeSupported(bt709Type);
      logResult(bt709Type, bt709Supported);

      if (bt709Supported) {
        return true;
      }
    },
    harnessConfig.support_hdr);

createSupportTest(
    '14.14.11.1',
    'Spherical(decode-to-texture) Support',
    function(logResult) {
      if (!(isTypeSupportedTest(logResult))) return false;

      var invalidSphericalType = util.createSimpleVideoFormatStr(
          'webm', 'vp9', 'decode-to-texture=nope');
      var invalidSphericalSupported =
          MediaSource.isTypeSupported(invalidSphericalType);
      logResult(invalidSphericalType, invalidSphericalSupported);

      var validSphericalTrueType = util.createSimpleVideoFormatStr(
          'webm', 'vp9', 'decode-to-texture=true');
      var validSphericalTrueSupported =
          MediaSource.isTypeSupported(validSphericalTrueType);
      logResult(validSphericalTrueType, validSphericalTrueSupported);

      var validSphericalFalseType = util.createSimpleVideoFormatStr(
          'webm', 'vp9', 'decode-to-texture=false');
      var validSphericalFalseSupported =
          MediaSource.isTypeSupported(validSphericalFalseType);
      logResult(validSphericalFalseType, validSphericalFalseSupported);

      if (validSphericalTrueSupported && validSphericalFalseSupported &&
          !invalidSphericalSupported) {
        return true;
      }
    },
    util.isCobalt());

/**
 * Test if specified type is supported through videoElement.isTypeSupported.
 */
var createEMETest = function(testId, name, evaluate, mandatory) {
  var test = createFunctionalTest(testId, name, 'EME Basic', mandatory);
  test.prototype.title = 'Test ' + name;
  test.prototype.start = function(runner, video) {
    try {
      if (evaluate(video)) {
        return runner.succeed();
      }
      throw name + ' failed.';
    } catch (e) {
      return runner.fail(e);
    }
  }
};

createEMETest(
    '14.15.1.1',
    'canPlayType.keySystem',
    function(videoElement) {
      return videoElement.canPlayType(
          util.createVideoFormatStr('mp4', 'avc1.42E01E'),
          'org.w3.clearkey') != undefined ||
              videoElement.canPlayType(
                  util.createVideoFormatStr('webm', 'vp9'),
                  'org.w3.clearkey') != undefined;
    });


var widevineTest = function(videoElement) {
  return videoElement.canPlayType(
      util.createVideoFormatStr('mp4', 'avc1.42E01E'),
      'com.widevine.alpha') != undefined ||
          videoElement.canPlayType(
              util.createVideoFormatStr('webm', 'vp9'),
              'com.widevine.alpha') != undefined;
};

createEMETest('14.15.2.1', 'Widevine', widevineTest);


var playreadyTest = function(videoElement) {
  return videoElement.canPlayType(
      util.createVideoFormatStr('mp4', 'avc1.42E01E'),
      'com.youtube.playready') != undefined;
};

createEMETest('14.15.3.1', 'PlayReady', playreadyTest, false);

createEMETest('14.15.4.1', 'DRM', function(videoElement) {
  return widevineTest || playreadyTest;
});

createEMETest(
    '14.15.5.1',
    'Video.generateKeyRequest',
    function(videoElement) {
      return Modernizr.prefixed(
          'generateKeyRequest', videoElement, false) != false;
    },
    false);

/**
 * Creates a MediaDevices test that validates whether the device has
 * Audio input media device and it implements correctly.
 */
var createMediaDevicesTest = function(testId) {
  var name = 'EnumerateDevices';
  var test =
      createFunctionalTest(testId, name, name, harnessConfig.support_webspeech);
  test.prototype.title = 'Test if ' + name + ' exists';
  test.prototype.start = function(runner) {
    try {
      if (undefined == navigator.mediaDevices.enumerateDevices) {
        throw Error('enumerateDevices does not exists!');
      }
      navigator.mediaDevices.enumerateDevices().then(function(listOfDevices) {
        for (var device of listOfDevices) {
          if (undefined == device.kind) {
            throw Error('kind does not exists!');
          }
          if (undefined == device.label) {
            throw Error('label does not exists!');
          }
          if (device.kind == "audioinput") {
            return runner.succeed();
          }
        }
        throw Error('No audioinput found!');
      });
    } catch (e) {
      return runner.fail(e);
    }
  };
};

createMediaDevicesTest('14.16.1.1');

/**
 * Create a UAString test that validates the format of current User Agent.
 */
var user_agent_2020 = new RegExp(
    [
      '([-.A-Za-z0-9\\\\\\/]+)_([-.A-Za-z0-9\\\\\\/]*)_([a-zA-Z0-9\\-]*)',
      '_2020 ?\\/ ?[-_.A-Za-z0-9\\\\]* \\(([a-zA-Z0-9\\-\\_]+), ',
      '?([a-zA-Z0-9\\-\\_]*), ?([WIREDLSwiredls\\\\\\/]*)\\)'
    ].join(''));

var createUAStringTest = function(testId) {
  var name = 'User Agent';
  var test = createFunctionalTest(testId, name, name);
  test.prototype.title = 'Test if ' + name + ' format is correct';
  test.prototype.start = function(runner) {
    try{
      var useragentParsed = user_agent_2020.exec(navigator.userAgent);
      if (useragentParsed &&
          useragentParsed[2].match(/^(BDP|GAME|OTT|STB|TV|ATV)$/)) {
        return runner.succeed();
      }
      throw Error('invalid User agent format');
    } catch (e) {
      return runner.fail(e);
    }
  };
};

createUAStringTest('14.17.1.2');

/**
 * Create a UAString test that validates version of certain items in user agent.
 */
var createCobaltVersionTest = function(testId, regexp, version, name) {
  var title = 'User Agent';
  var test = createFunctionalTest(testId, name + ' ' + title, title);
  test.prototype.title = 'Test if ' + name + title + ' format is correct';
  test.prototype.start = function(runner) {
    try {
      var useragentParsed = regexp.exec(navigator.userAgent);
      if (name === 'Cobalt') {
        var xhr = new XMLHttpRequest();
        xhr.open('GET', COBALT_REQ_URL);
        xhr.send();
        xhr.onreadystatechange = function() {
          if (xhr.readyState === 4) {
            if (xhr.status === 200) {
              try {
                var buildVersion = Number(window.atob(xhr.responseText));
              } catch (e) {
                runner.fail(e);
              }
              if (useragentParsed &&
                  Number(useragentParsed[1].split('.')[1]) === buildVersion) {
                return runner.succeed();
              }
              runner.fail(Error(name + ' version is not ' + buildVersion));
            } else {
              runner.fail(Error('Failed to request Cobalt build version'));
            }
          }
        };
      } else {
        if (useragentParsed && Number(useragentParsed[1]) >= version) {
          return runner.succeed();
        }
        throw Error(name + ' version is less than ' + version);
      }
    } catch (e) {
      return runner.fail(e);
    }
  };
};

createCobaltVersionTest(
    '14.17.2.1', new RegExp(/20\.lts\.([0-9]\.[0-9]{6})/), 0, 'Cobalt');
createCobaltVersionTest(
    '14.17.3.2', new RegExp(/Starboard\/([0-9]+)/), 11, 'Starboard')

var widevine_ua = /([-.A-Za-z0-9\\\/]*)_([-.A-Za-z0-9\\\/]*)_([-.A-Za-z0-9\\\/]*) ?\/ ?[-_.A-Za-z0-9\\]* \(([-_.A-Za-z0-9\\\/ ]+), ?([^,]*), ?([WIREDLSwiredls\\\/]*)\)/;

/**
 * Ensure information in Widevine License match User Agent and the OEMCrypto
 * api version is at least 15.
 */
var testWidevine = createFunctionalTest('14.17.4.1', 'Widevine License', 'User Agent');
testWidevine.prototype.title = 'Test if widevine versions.'
testWidevine.prototype.start = function(runner) {

  function clearPlayer() {
    if((typeof player !== 'undefined') && (player !== null)){
      player.unload();
      player.destroy();
    }
  };

  try {
    var manifestUri =
    'test-materials/media/manual/wv_license_request.mpd';
    var licenseServer = 'https://cwip-shaka-proxy.appspot.com/no_auth';
    var userAgentParsed = widevine_ua.exec(navigator.userAgent);
    if (!userAgentParsed) {
      throw Error('Error! User agent is not in correct format');
    }
    var ua_brand = userAgentParsed[4];
    var ua_model = userAgentParsed[5];

    var sessionID;
    const initSession = () => {
      /**
       * Generates a GUID string, according to RFC4122 standards.
       * @returns {String} The generated GUID.
       * @example af8a84166e18a307bd9cf2c947bbb3aa
       * @author Slavik Meltser (slavik@meltser.info).
       * @link http://slavik.meltser.info/?p=142
       */
      function _p8(s) {
        var p = (Math.random().toString(16) + '000000000').substr(2, 8);
        return s ? p.substr(0, 4) + p.substr(4, 4) : p;
      }
      sessionID = window.location.href.split('testid=')[1] ||
          _p8() + _p8(true) + _p8(true) + _p8();
      runner.log('Session ID: ' + sessionID);
    };

    const initPlayer = () => {
      // Create a Player instance.
      var videoElement = createElement('video');
      try {
        videoElement.textTracks = [];
      } catch (e) {
        let textTracks = videoElement.textTracks;
        textTracks = [];
      }
      videoElement.addTextTrack = function() {
        return {
          addCue: function() {}
        }
      };
      var player = new shaka.Player(videoElement);

      // Attach player to the window to make it easy to access in the JS console.
      window.player = player;

      // Listen for error events.
      player.addEventListener('error', function(event) {
        throw Error('shaka error ' + event.detail);
      });

      // Try to load a manifest.
      // This is an asynchronous process.

      player.configure({drm: {servers: {'com.widevine.alpha': licenseServer}}});
      player.getNetworkingEngine().registerRequestFilter(function(
          type, request) {
        // Only manipulate license requests:
        if (type == shaka.net.NetworkingEngine.RequestType.LICENSE) {
          // This is the raw license request generated by the Widevine CDM.
          var rawLicenseRequest = new Uint8Array(request.body);
          // Encode the raw license request in base64.

          var rawLicenseRequestBase64 =
              base64js.fromByteArray(rawLicenseRequest);
          runner.log('license request: ' + rawLicenseRequestBase64);

          var xhr = new XMLHttpRequest();
          var url = 'https://proxy.uat.widevine.com/proxy?get_client_id=true';
          xhr.open('POST', url, true);
          xhr.send(rawLicenseRequest);
          xhr.onreadystatechange = function() {
            try {
              if (xhr.readyState == 4) {
                if (xhr.status != 200) {
                  throw Error('failed, HTTP status ' + xhr.status);
                } else {
                  runner.log("xhr.responseText: " + xhr.responseText);
                  var parsedResponse = JSON.parse(xhr.responseText);
                  var license = {};
                  var client_info = parsedResponse.client_info;
                  if (client_info.length != 0) {
                    for (var i = 0; i < client_info.length; i++) {
                      var key = parsedResponse.client_info[i].name;
                      var value = parsedResponse.client_info[i].value;
                      license[key] = value;
                    }
                    runner.checkEq(
                        license["company_name"], ua_brand, "Company Name");
                    runner.checkEq(license["model_name"], ua_model, "Model Name");
                    runner.checkGE(
                        parsedResponse.client_capabilities.oem_crypto_api_version,
                        15, "OEMCrypto version");
                    runner.succeed();
                    clearPlayer();
                  }
                }
              }
            } catch (error) {
              throw Error('Player Error ' + error);
            }
          };
        }
      });
      player.load(manifestUri).then(function() {}).catch(function(error) {
        throw Error('Load error ' + error);
      });
    };


    initSession();
    // Install built-in polyfills to patch browser incompatibilities.
    shaka.polyfill.installAll();

    // Check to see if the browser supports the basic APIs Shaka needs.
    if (shaka.Player.isBrowserSupported()) {
      // Everything looks good!
      initPlayer();
    } else {
      // This browser does not have the minimum set of APIs we need.
      throw Error('Browser not supported!');
    }
  } catch (e) {
    return runner.fail(e);
    clearPlayer();
  }
};

/**
 * Create a CPU memory test that validates the size of system memory.
 */
var createMemoryTest = function(testId, processor, size) {
  var title = processor + ' System Memory';
  var test = createFunctionalTest(testId, title, 'Memory Allocation');
  test.prototype.title = 'Test if ' + title + ' is correct';
  test.prototype.start = function(runner) {
    try{
      if (typeof h5vcc != 'undefined' && typeof h5vcc.cVal != 'undefined') {
        var memFree = Number(h5vcc.cVal.getValue('Memory.' + processor +
            '.Free'));
        var memUsed = Number(h5vcc.cVal.getValue('Memory.' + processor +
            '.Used'));
        if (memFree + memUsed < size) {
          throw Error(processor + ' memory allocated less than ' + size);
        }
      } else {
        throw Error('Cobalt not found');
      }
      return runner.succeed();
    } catch (e) {
      return runner.fail(e);
    }
  };
};

createMemoryTest('14.18.1.1','CPU', 170 * 1024 * 1024);

/**
 * Create a memory test that validates the size of JavaScript allocation.
 */
var createMemoryAllocationTest = function(testId, size) {
  var title = 'JavaScript Memory Allocation';
  var test = createFunctionalTest(testId, title, 'Memory Allocation');
  test.prototype.title = 'Test if ' + title + ' is correct';
  test.prototype.start = function(runner) {
    try{
      var a = new ArrayBuffer(size * 1024 * 1024);
      return runner.succeed();
    } catch (e) {
      return runner.fail(e);
    }
  };
};

createMemoryAllocationTest('14.18.2.1', 80);

var GetMinVideoBufferSizeInMB = function() {
  if (util.is8k()) {
    return 300;
  } else if (util.is4k()) {
    return util.supportHdr() ? 80 : 50;
  } else {
    return 30;
  }
};

var GetVideoSrc = function() {
  if (util.is4k()) {
    return util.supportHdr() ?
        Media.VP9.Video2160pHdr1MB : Media.VP9.Video2160p1MB;
  } else {
    return Media.VP9.Video1080p1MB;
  }
}

/**
 * Test if device can hold a minimum size of video and audiosource buffer.
 */
var testSourceBufferSize = createFunctionalTest(
    '14.19.1.1', 'Source Buffer Size', 'Buffer', util.isGtFHD());
testSourceBufferSize.prototype.title = 'Determines video and audiobuffer sizes '
    'by appending incrementally until discard occurs or requirement is met.';
testSourceBufferSize.prototype.start = function(runner, video) {
    var ms = new MediaSource();
    // We start appending video clip repeatedly until we get eviction.
    var videoStream = GetVideoSrc();
    // The audio clip has just over 5MB, which is the requirement for audio
    // souce buffer.
    var audioStream = Media.AAC.AudioHuge;
    var videoSb;
    var audioSb;
    ms.addEventListener('sourceopen', function() {
      videoSb = ms.addSourceBuffer(videoStream.mimetype);
      audioSb = ms.addSourceBuffer(audioStream.mimetype);
    });
    video.src = window.URL.createObjectURL(ms);

    var minVideoBufferSizeMb = GetMinVideoBufferSizeInMB();
    var minVideoBufferSize = minVideoBufferSizeMb * 1024 * 1024;
    var videoEstimatedMinTime =
        minVideoBufferSize * videoStream.duration / videoStream.size * 0.9;

    var videoXhr =
        runner.XHRManager.createRequest(videoStream.src, function(e) {
      var appendCount = 0;
      var expectedTime = 0;
      var expectedSize = 0;

      var onUpdate = function() {
        appendCount++;
        runner.log('Append count ' + appendCount);
        if (videoSb.buffered.start(0) > 0 ||
            expectedTime > videoSb.buffered.end(0)) {
          onBufferFull();
        } else {
          expectedTime += videoStream.duration;
          expectedSize += videoStream.size;
          if (expectedSize > minVideoBufferSize) {
            runner.log('Source buffer exceeded minimum: ' + minVideoBufferSize);
            onBufferFull();
            return;
          }
          videoSb.timestampOffset = expectedTime;
          try {
            videoSb.appendBuffer(videoXhr.getResponseData());
          } catch (e) {
            runner.log(e);
            var QUOTA_EXCEEDED_ERROR_CODE = 22;
            if (e.code == QUOTA_EXCEEDED_ERROR_CODE) {
              onBufferFull();
            } else {
              runner.fail(e);
            }
          }
        }
      };
      var onBufferFull = function() {
        videoSb.removeEventListener('updateend', onUpdate);
        runner.checkGE(
            videoSb.buffered.end(0) - videoSb.buffered.start(0),
            videoEstimatedMinTime,
            'Time range in source buffer');
        runner.succeed();
      };
      videoSb.addEventListener('updateend', onUpdate);
      videoSb.appendBuffer(videoXhr.getResponseData());
    });

    var audioXhr =
        runner.XHRManager.createRequest(audioStream.src, function(e) {
      var minAudioBufferSizeMb = 5;
      var minAudioBufferSize = minAudioBufferSizeMb * 1024 * 1024;
      var audioEstimatedMinTime =
          minAudioBufferSize * (audioStream.duration / audioStream.size) * 0.95;
      var onUpdate = function() {
        audioSb.removeEventListener('updateend', onUpdate);
        runner.checkGE(
            audioSb.buffered.end(0) - audioSb.buffered.start(0),
            audioEstimatedMinTime,
            'Time range in source buffer');
        videoXhr.send();
      };
      audioSb.addEventListener('updateend', onUpdate);
      audioSb.appendBuffer(audioXhr.getResponseData());
    });
    audioXhr.send();
};

/**
 * Ensure Same-origin Policy is enabled through accessing localStorage.
 */
var testSameOriginPolicy =
    createFunctionalTest('14.20.1.1', 'Same-origin Policy', 'Assorted');
testSameOriginPolicy.prototype.title = 'Test Same-origin Policy.';
testSameOriginPolicy.prototype.start = function(runner) {
  var value = localStorage.getItem('yt.leanback::schema-version');
  runner.log('[Same-origin Policy] - localStorage.getItem(' +
      '"yt.leanback::schema-version"); returned: ' + value);
  runner.checkEq(
      value, null, 'localStorage.getItem("yt.leanback::schema-version")');
  return runner.succeed();
};


var createFontDiv = function(format, content) {
  var span = createElement('span');
  span.className = 'dancing-script-' + format;
  span.innerHTML = content;
  return span;
};

/**
 * Validate specified font format is supported by browser.
 * Assume ttf is supported.
 */
var createFontTest = function(testId, format) {
  var test = createFunctionalTest(testId, 'Fonts - ' + format, 'Fonts', false);
  test.prototype.title = 'Test if font format ' + format + ' is supported.';
  test.prototype.start = function(runner) {
    try {
      testArea.removeAttribute('style');
      var fontDiv = createElement('div');
      fontDiv.className = 'font-block';
      testArea.appendChild(fontDiv);
      var characters =
      'ABCĆČDĐEFGHIJKLMNOPQRSŠTUVWXYZŽabcčćdđefghijklmnopqrsštuvwxyzž' +
      'ĂÂÊÔƠƯăâêôơư1234567890‘?’“!”(%)[#]{@}/&amp;\&lt;-+÷×=&gt;®©$€£¥¢:;,.*';
      var ttfEle = createFontDiv('ttf', characters)
      var testEle = createFontDiv(format, characters)
      fontDiv.appendChild(ttfEle);
      fontDiv.appendChild(testEle);

      // Cobalt does not emit any event when fonts load finish.
      // So use setTimeout as a workaround.
      setTimeout(function(event) {
        var ttfWidth = ttfEle.offsetWidth;
        var testWidth = testEle.offsetWidth;
        testArea.removeChild(fontDiv);
        testArea.setAttribute('style', 'display: none');
        if (ttfWidth !== testWidth) {
          throw 'Widths don\'t match: ttf:' + ttfWidth + ' and ' +
              format + ':' + testWidth;
        }
        return runner.succeed();
      }, MS_TO_WAIT_FOR_FONTS);
    } catch (e) {
      return runner.fail(e);
    }
  };
};

createFontTest('14.21.1.1', 'woff');
createFontTest('14.21.2.1', 'woff2');


var setCookie = function(key, value, path, domain, expires, httponly) {
  var payload = key + '=' + value;
  payload += '; path=' + (path ? path : '/');
  payload += '; domain=' + (domain ? domain : '.ytlr-cert.appspot.com');
  if (expires) {
    payload += '; expires=' + expires;
  }
  if (httponly) {
    payload += '; httponly';
  }
  document.cookie = payload;
}

var setCookieObj = function(cookie) {
  setCookie(
      cookie['key'],
      cookie['value'],
      cookie['path'],
      cookie['domain'],
      cookie['expires'],
      cookie['httponly']);
};

var getCookieObject = function() {
  var arr = document.cookie.split('; ');
  var result = {};
  for (var i = 0; i < arr.length; i++) {
    var kv = arr[i].split('=');
    if (kv.length > 1) {
      result[kv[0]] = kv[1];
    }
  }
  return result;
};

// Clear cookie by marking it as expired.
var clearCookie = function(cookie) {
  setCookie(
      cookie['key'],
      cookie['value'],
      cookie['path'],
      cookie['domain'],
      'Thu, 01-Jan-1970 00:00:00 GMT',
      cookie['httponly']);
};

var clearTestCookies = function() {
  for (var i in valid_cookies) {
    clearCookie(valid_cookies[i]);
  }
  for (var i in invalid_cookies) {
    clearCookie(invalid_cookies[i]);
  }
};

var getCookiePath = function() {
  return (testVersion != 'Test-In-Progress') ? testVersion : 'tip';
};

// 'valid_path_cookie' and 'valid_domain_cookie' would fail if you download
// the source code and setup your own server
var valid_cookies = [
  {key: 'valid_cookie', value: 'valid'},
  {key: 'valid_path_cookie', value: 'valid', path: getCookiePath()},
  {
    key: 'valid_domain_cookie',
    value: 'valid',
    domain: '.ytlr-cert.appspot.com'
  }
];

var invalid_cookies = [
  {key: 'invalid_http_only', value: 'invalid', httponly: true},
  {key: 'invalid_path_cookie', value: 'invalid', path: '/p/a/t/h'},
  {
    key: 'invalid_domain_cookie',
    value: 'invalid',
    domain: '.invalid-domain.com'
  },
  {
    key: 'invalid_expired_cookie',
    value: 'invalid',
    expires: 'Thu, 01-Jan-1970 00:00:00 GMT'
  }
];

/**
 * Ensure browser could set cookie and save properly.
 */
var testSetCookie = createFunctionalTest('14.22.1.1', 'Set Cookie', 'Cookie');
testSetCookie.prototype.title = 'Test if browser could save cookie.'
testSetCookie.prototype.start = function(runner) {
  try {
    for (var i in valid_cookies) {
      setCookieObj(valid_cookies[i]);
    }
    for (var i in invalid_cookies) {
      setCookieObj(invalid_cookies[i]);
    }

    var keys = Object.keys(getCookieObject());

    for (var i in valid_cookies) {
      var k = valid_cookies[i].key;
      if (!keys.includes(k)) {
        throw 'Key ' + k + ' should exist in document.cookie but it does not.';
      }
    }
    for (var i in invalid_cookies) {
      var k = invalid_cookies[i].key;
      if (keys.includes(k)) {
        throw 'Key ' + k + ' should not exist in document.cookie but it does.';
      }
    }
    return runner.succeed();
  } catch (e) {
    return runner.fail(e);
  } finally {
    clearTestCookies();
  }
};

/**
 * Ensure browser could set cookie and save properly.
 */
var testSetExpiredCookie =
    createFunctionalTest('14.22.2.1', 'Set Expired Cookie', 'Cookie');
testSetExpiredCookie.prototype.title = 'Test if browser ignores expired cookie.'
testSetExpiredCookie.prototype.start = function(runner) {
  try {
    for (var i in valid_cookies) {
      setCookieObj(valid_cookies[i]);
    }

    var keys = Object.keys(getCookieObject());
    for (var i in valid_cookies) {
      var k = valid_cookies[i].key;
      if (!keys.includes(k)) {
        throw 'Key ' + k + ' should exist in document.cookie but it does not.';
      }
    }

    for (var i in valid_cookies) {
      clearCookie(valid_cookies[i]);
    }

    keys = Object.keys(getCookieObject());
    for (var i in valid_cookies) {
      var k = valid_cookies[i].key;
      if (keys.includes(k)) {
        throw 'Key ' + k + ' should not exist in document.cookie but it does.';
      }
    }

    return runner.succeed();
  } catch (e) {
    return runner.fail(e);
  } finally {
    clearTestCookies();
  }
};

/**
 * Validate certain number of cookies is supported by browser.
 */
var createCookieStorageTest = function(testId, name, count) {
  var test = createFunctionalTest(testId, name, 'Cookie');
  test.prototype.title = 'Test if browser supports ' + count + '4kb cookies.';
  test.prototype.start = function(runner) {
    var cookie_size = 4000;
    try {
      var cookie_storage = document.cookie.length;
      for (var i = 0; i < count; i++) {
        var prefix = 'CookieStorageTest' + ('000' + i).substr(-3) + '=';
        var payload =
            prefix + new Array(cookie_size - prefix.length + 1).join('m');
        document.cookie = payload;
        if (document.cookie.length - cookie_storage < cookie_size)
          throw 'Fail to add the No.' + i + ' cookie';
        else
          cookie_storage = document.cookie.length;
      }
      return runner.succeed();
    } catch (e) {
      return runner.fail(e);
    } finally {
      document.cookie.split(';').forEach(function(c) {
        if (c.trim().startsWith('CookieStorageTest')) {
          document.cookie = c.replace(/^ +/, '').replace(/=.*/, '=;expires=' +
              new Date().toUTCString());
        }
      });
    }
  };
};

createCookieStorageTest('14.22.3.1', 'Min Cookie Storage', 50);
createCookieStorageTest('14.22.4.1', 'Max Cookie Storage', 150);

/**
 * Validate browser could correctly handle request from server with valid and
 * invalid certificates.
 */
var createSslTest = function(testId, name, origin, shouldFail = false) {
  var test = createFunctionalTest(testId, name, 'SSL');
  test.prototype.title =
      'Test if browser correctly handle to request to ' + origin;
  test.prototype.start = function(runner) {
    var url = origin + 'test/dashboard/small-image.png';
    var img = new Image();

    img.addEventListener('load', function() {
      if (shouldFail) {
        return runner.fail(
            'Request to website with invalid SSL certificate succeeds.' +
            'Invalid SSL requests must not be allowed to load.');
      }
      return runner.succeed();
    }, false);

    img.addEventListener('error', function(event) {
      if (shouldFail) {
        return runner.succeed();
      }
      return runner.fail('Request to website with valid SSL certificate fails.');
    }, false);

    img.src = url;
  };
};

createSslTest('14.23.1.1', 'Self-Signed', 'https://self-signed.badssl.com/', true);
createSslTest('14.23.2.1', 'expired', 'https://expired.badssl.com/', true);
createSslTest('14.23.3.1', 'sha256', 'https://sha256.badssl.com/');
createSslTest('14.23.4.1', 'TLS', 'https://tls-v1-2.badssl.com:1012/');

var testGlobalSignR2 =
    createFunctionalTest('14.23.5.1', 'GlobalSign RootCA R2', 'SSL');
testGlobalSignR2.prototype.title = 'Test if browser correctly handle to' +
    'request with GlobalSign RootCA R2 certificate.';
testGlobalSignR2.prototype.start = function(runner) {
  try {
    var xhr = new XMLHttpRequest();
    xhr.open('GET', 'https://cert-test.sandbox.google.com/', true);
    xhr.send();
    xhr.onreadystatechange = function() {
      if (xhr.readyState == 4) {
        if (xhr.status != 200) {
          runner.fail(Error('Xhr request status is not 200 ok'));
        }
        if (!xhr.responseText.includes('Client test successful.')) {
          runner.fail(
              Error('XHR Response not as expected: ' + xhr.responseText));
        }
        return runner.succeed();
      }
    };
  } catch (e) {
    return runner.fail(e);
  }
};

var testGlobalSignR3 =
    createFunctionalTest('14.23.6.1', 'GlobalSign RootCA R3', 'SSL');
testGlobalSignR3.prototype.title = 'Test if browser correctly handle to' +
    'request with GlobalSign RootCA R3 certificate.';
testGlobalSignR3.prototype.start = function(runner) {
  try {
    var url =
	"https://www.globalsign.com/files/4114/9494/4842/switch-to-secure-ssl-globalsign.jpg"
    var img = new Image();

    img.addEventListener('load', function() {
      return runner.succeed();
    }, false);

    img.addEventListener('error', function(event) {
      runner.fail(Error('Request to website with valid SSL certificate fails.'));
    }, false);

    img.src = url;
  } catch (e) {
    return runner.fail(e);
  }
};

return {tests: tests, info: info, fields: fields, viewType: 'default'};
};