trisquel-icecat/icecat/debian/tests/data/HTML5test/scripts/8/engine.js

4563 lines
136 KiB
JavaScript

Test =
Test8 = (function () {
var release = 8;
var NO = 0,
YES = 1,
OLD = 2,
BUGGY = 4,
PREFIX = 8,
BLOCKED = 16,
DISABLED = 32,
UNCONFIRMED = 64,
UNKNOWN = 128,
EXPERIMENTAL = 256;
var blacklists = [];
var testsuite = [
/* doctype */
function (results) {
results.addItem({
key: 'parsing.doctype',
passed: document.compatMode == 'CSS1Compat'
});
},
/* tokenizer */
function (results) {
var result = true;
var e = document.createElement('div');
try {
e.innerHTML = "<div<div>";
result &= e.firstChild && e.firstChild.nodeName == "DIV<DIV";
e.innerHTML = "<div foo<bar=''>";
result &= e.firstChild.attributes[0].nodeName == "foo<bar" || e.firstChild.attributes[0].name == "foo<bar";
e.innerHTML = "<div foo=`bar`>";
result &= e.firstChild.getAttribute("foo") == "`bar`";
e.innerHTML = "<div \"foo=''>";
result &= e.firstChild && (e.firstChild.attributes[0].nodeName == "\"foo" || e.firstChild.attributes[0].name == "\"foo");
e.innerHTML = "<a href='\nbar'></a>";
result &= e.firstChild && e.firstChild.getAttribute("href") == "\nbar";
e.innerHTML = "<!DOCTYPE html>";
result &= e.firstChild == null;
e.innerHTML = "\u000D";
result &= e.firstChild && e.firstChild.nodeValue == "\u000A";
e.innerHTML = "&lang;&rang;";
result &= e.firstChild.nodeValue == "\u27E8\u27E9";
e.innerHTML = "&apos;";
result &= e.firstChild.nodeValue == "'";
e.innerHTML = "&ImaginaryI;";
result &= e.firstChild.nodeValue == "\u2148";
e.innerHTML = "&Kopf;";
result &= e.firstChild.nodeValue == "\uD835\uDD42";
e.innerHTML = "&notinva;";
result &= e.firstChild.nodeValue == "\u2209";
e.innerHTML = '<?import namespace="foo" implementation="#bar">';
result &= e.firstChild && e.firstChild.nodeType == 8 && e.firstChild.nodeValue == '?import namespace="foo" implementation="#bar"';
e.innerHTML = '<!--foo--bar-->';
result &= e.firstChild && e.firstChild.nodeType == 8 && e.firstChild.nodeValue == 'foo--bar';
e.innerHTML = '<![CDATA[x]]>';
result &= e.firstChild && e.firstChild.nodeType == 8 && e.firstChild.nodeValue == '[CDATA[x]]';
e.innerHTML = "<textarea><!--</textarea>--></textarea>";
result &= e.firstChild && e.firstChild.firstChild && e.firstChild.firstChild.nodeValue == "<!--";
e.innerHTML = "<textarea><!--</textarea>-->";
result &= e.firstChild && e.firstChild.firstChild && e.firstChild.firstChild.nodeValue == "<!--";
e.innerHTML = "<style><!--</style>--></style>";
result &= e.firstChild && e.firstChild.firstChild && e.firstChild.firstChild.nodeValue == "<!--";
e.innerHTML = "<style><!--</style>-->";
result &= e.firstChild && e.firstChild.firstChild && e.firstChild.firstChild.nodeValue == "<!--";
} catch (e) {
result = false;
}
results.addItem({
key: 'parsing.tokenizer',
passed: result
});
},
/* tree builder */
function (results) {
var result = true;
var e = document.createElement('div');
try {
var h = document.createElement("html");
h.innerHTML = "";
result &= h.firstChild && h.firstChild.nodeName == "HEAD" && h.lastChild.nodeName == "BODY" && h.firstChild.nextSibling == h.lastChild;
} catch (e) {
result = false;
}
try {
var t = document.createElement("table");
t.innerHTML = "<col>";
result &= t.firstChild && t.firstChild.nodeName == "COLGROUP";
} catch (e) {
result = false;
}
e.innerHTML = "<ul><li>A </li> <li>B</li></ul>";
result &= e.firstChild && e.firstChild.firstChild && e.firstChild.firstChild.firstChild && e.firstChild.firstChild.firstChild.nodeValue == "A ";
e.innerHTML = "<table><form><input type=hidden><input></form><div></div></table>";
result &= e.firstChild &&
e.firstChild.nodeName == "INPUT" &&
e.firstChild.nextSibling &&
e.firstChild.nextSibling.nodeName == "DIV" &&
e.lastChild.nodeName == "TABLE" &&
e.firstChild.nextSibling.nextSibling == e.lastChild &&
e.lastChild.firstChild &&
e.lastChild.firstChild.nodeName == "FORM" &&
e.lastChild.firstChild.firstChild == null &&
e.lastChild.lastChild.nodeName == "INPUT" &&
e.lastChild.firstChild.nextSibling == e.lastChild.lastChild;
e.innerHTML = "<i>A<b>B<p></i>C</b>D";
result &= e.firstChild &&
e.childNodes.length == 3 &&
e.childNodes[0].nodeName == "I" &&
e.childNodes[0].childNodes.length == 2 &&
e.childNodes[0].childNodes[0].nodeValue == "A" &&
e.childNodes[0].childNodes[1].nodeName == "B" &&
e.childNodes[0].childNodes[1].childNodes.length == 1 &&
e.childNodes[0].childNodes[1].childNodes[0].nodeValue == "B" &&
e.childNodes[1].nodeName == "B" &&
e.childNodes[1].firstChild == null &&
e.childNodes[2].nodeName == "P" &&
e.childNodes[2].childNodes.length == 2 &&
e.childNodes[2].childNodes[0].nodeName == "B" &&
e.childNodes[2].childNodes[0].childNodes.length == 2 &&
e.childNodes[2].childNodes[0].childNodes[0].nodeName == "I" &&
e.childNodes[2].childNodes[0].childNodes[0].firstChild == null &&
e.childNodes[2].childNodes[0].childNodes[1].nodeValue == "C" &&
e.childNodes[2].childNodes[1].nodeValue == "D";
e.innerHTML = "<div></div>";
result &= e.firstChild && "namespaceURI" in e.firstChild && e.firstChild.namespaceURI == "http://www.w3.org/1999/xhtml";
results.addItem({
key: 'parsing.tree',
passed: result
});
},
/* svg in html */
function (results) {
var e = document.createElement('div');
e.innerHTML = '<svg></svg>';
var passed = e.firstChild && "namespaceURI" in e.firstChild && e.firstChild.namespaceURI == 'http://www.w3.org/2000/svg';
results.addItem({
key: 'parsing.svg',
passed: passed
});
},
/* svg in html */
function (results) {
var e = document.createElement('div');
e.innerHTML = '<math></math>';
var passed = e.firstChild && "namespaceURI" in e.firstChild && e.firstChild.namespaceURI == 'http://www.w3.org/1998/Math/MathML';
results.addItem({
key: 'parsing.mathml',
passed: passed
});
},
/* dataset */
function (results) {
var element = document.createElement('div');
element.setAttribute('data-test', 'test');
results.addItem({
key: 'elements.dataset',
passed: 'dataset' in element
});
},
/* section, nav, article, header and footer */
function (results) {
var elements = 'section nav article aside header footer'.split(' ');
for (var e = 0; e < elements.length; e++) {
var passed = false;
try {
var element = document.createElement(elements[e]);
document.body.appendChild(element);
try {
passed = element instanceof HTMLElement && !(element instanceof HTMLUnknownElement) && isBlock(element) && closesImplicitly(elements[e]);
} catch (error) {
}
document.body.removeChild(element);
} catch (error) {
}
results.addItem({
key: 'elements.section.' + elements[e],
passed: passed,
value: 1
});
}
},
/* main, figure and figcaption */
function (results) {
var elements = 'main figure figcaption'.split(' ');
for (var e = 0; e < elements.length; e++) {
var passed = false;
try {
var element = document.createElement(elements[e]);
document.body.appendChild(element);
try {
passed = element instanceof HTMLElement && !(element instanceof HTMLUnknownElement) && isBlock(element) && (elements[e] != 'figure' || closesImplicitly(elements[e]));
} catch (error) {
}
document.body.removeChild(element);
} catch (error) {
}
results.addItem({
key: 'elements.grouping.' + elements[e],
passed: passed
});
}
},
/* ol grouping */
function (results) {
results.addItem({
key: 'elements.grouping.ol',
passed: 'reversed' in document.createElement('ol')
});
},
/* a download */
function (results) {
results.addItem({
key: 'elements.semantic.download',
passed: 'download' in document.createElement('a')
});
},
/* a ping */
function (results) {
results.addItem({
key: 'elements.semantic.ping',
passed: 'ping' in document.createElement('a')
});
},
/* mark element */
function (results) {
var passed = false;
try {
var element = document.createElement('mark');
document.body.appendChild(element);
try {
passed = element instanceof HTMLElement && !(element instanceof HTMLUnknownElement) && (color = getStyle(element, 'background-color')) && (color != 'transparent');
} catch (error) {
}
document.body.removeChild(element);
} catch (error) {
}
results.addItem({
key: 'elements.semantic.mark',
passed: passed
});
},
/* ruby, rt, rp element */
function (results) {
var container = document.createElement('div');
document.body.appendChild(container);
container.innerHTML = "<ruby id='ruby'><rp id='rp'></rp><rt id='rt'></rt></ruby>";
var rubyElement = document.getElementById('ruby');
var rtElement = document.getElementById('rt');
var rpElement = document.getElementById('rp');
var rubySupport = false;
var rtSupport = false;
var rpSupport = false;
try {
rubySupport = rubyElement && rubyElement instanceof HTMLElement && !(rubyElement instanceof HTMLUnknownElement);
rtSupport = rtElement && rtElement instanceof HTMLElement && !(rtElement instanceof HTMLUnknownElement);
rpSupport = rpElement && rpElement instanceof HTMLElement && !(rpElement instanceof HTMLUnknownElement) && isHidden(rpElement);
} catch (error) {
}
document.body.removeChild(container);
results.addItem({
key: 'elements.semantic.ruby',
passed: rubySupport && rtSupport && rpSupport
});
},
/* time element */
function (results) {
var passed = false;
try {
var element = document.createElement('time');
try {
passed = typeof HTMLTimeElement != 'undefined' && element instanceof HTMLTimeElement;
} catch (error) {
}
} catch (error) {
}
results.addItem({
key: 'elements.semantic.time',
passed: passed
});
},
/* data element */
function (results) {
var passed = false;
try {
var element = document.createElement('data');
try {
passed = typeof HTMLDataElement != 'undefined' && element instanceof HTMLDataElement;
} catch (error) {
}
} catch (error) {
}
results.addItem({
key: 'elements.semantic.data',
passed: passed
});
},
/* wbr element */
function (results) {
var passed = false;
try {
var element = document.createElement('wbr');
try {
passed = element instanceof HTMLElement && !(element instanceof HTMLUnknownElement);
} catch (error) {
}
} catch (error) {
}
results.addItem({
key: 'elements.semantic.wbr',
passed: passed
});
},
/* details element */
function (results) {
var passed = false;
try {
var element = document.createElement('details');
element.innerHTML = '<summary>a</summary>b';
document.body.appendChild(element);
var height = element.offsetHeight;
element.open = true;
passed = height != element.offsetHeight;
document.body.removeChild(element);
} catch (error) {
}
results.addItem({
key: 'elements.interactive.details',
passed: passed
});
},
/* summary element */
function (results) {
var passed = false;
try {
var element = document.createElement('summary');
document.body.appendChild(element);
try {
passed = element instanceof HTMLElement && !(element instanceof HTMLUnknownElement);
} catch (error) {
}
document.body.removeChild(element);
} catch (error) {
}
results.addItem({
key: 'elements.interactive.summary',
passed: passed
});
},
/* menu toolbar */
function (results) {
var passed = legacy = false;
try {
var element = document.createElement('menu');
document.body.appendChild(element);
try {
legacy = typeof HTMLMenuElement != 'undefined' && element instanceof HTMLMenuElement && 'type' in element;
} catch (error) {
}
// Check default type
if (legacy && element.type != 'list') legacy = false;
// Check type sanitization
try {
element.type = 'foobar';
} catch (error) {
}
if (legacy && element.type == 'foobar') legacy = false;
// Check if correct type sticks
try {
element.type = 'list';
} catch (error) {
legacy = false;
}
if (legacy && element.type != 'list') legacy = false;
document.body.removeChild(element);
} catch (error) {
}
try {
var element = document.createElement('menu');
document.body.appendChild(element);
try {
passed = typeof HTMLMenuElement != 'undefined' && element instanceof HTMLMenuElement && 'type' in element;
} catch (error) {
}
// Check default type
if (passed && element.type != 'toolbar') passed = false;
// Check type sanitization
try {
element.type = 'foobar';
} catch (error) {
}
if (passed && element.type == 'foobar') passed = false;
// Check if correct type sticks
try {
element.type = 'toolbar';
} catch (error) {
passed = false;
}
if (passed && element.type != 'toolbar') passed = false;
document.body.removeChild(element);
} catch (error) {
}
results.addItem({
key: 'elements.interactive.menutoolbar',
passed: passed ? YES : legacy ? YES | OLD : NO
});
},
/* menu context */
function (results) {
var passed = legacy = false;
try {
var element = document.createElement('menu');
document.body.appendChild(element);
try {
legacy = typeof HTMLMenuElement != 'undefined' && element instanceof HTMLMenuElement && 'type' in element;
} catch (error) {
}
// Check if correct type sticks
try {
element.type = 'popup';
} catch (error) {
legacy = false;
}
if (legacy && element.type != 'popup') legacy = false;
if (legacy) {
var item = document.createElement('menuitem');
element.appendChild(item);
if (typeof HTMLMenuItemElement == 'undefined' || !item instanceof HTMLMenuItemElement) legacy = false;
}
document.body.removeChild(element);
} catch (error) {
}
try {
var element = document.createElement('menu');
document.body.appendChild(element);
try {
passed = typeof HTMLMenuElement != 'undefined' && element instanceof HTMLMenuElement && 'type' in element;
} catch (error) {
}
try {
element.type = 'context';
} catch (error) {
}
// Check default type
var second = document.createElement('menu');
element.appendChild(second);
if (passed && second.type == 'list') legacy = true;
if (passed && second.type != 'context') passed = false;
element.removeChild(second);
// Check type sanitization
try {
element.type = 'foobar';
} catch (error) {
}
if (passed && element.type == 'foobar') passed = false;
// Check if correct type sticks
try {
element.type = 'context';
} catch (error) {
passed = false;
}
if (passed && element.type != 'context') passed = false;
if (passed) {
var item = document.createElement('menuitem');
element.appendChild(item);
if (typeof HTMLMenuItemElement == 'undefined' || !item instanceof HTMLMenuItemElement) passed = false;
}
document.body.removeChild(element);
} catch (error) {
}
results.addItem({
key: 'elements.interactive.menucontext',
passed: passed ? YES : legacy ? YES | OLD : NO
});
},
/* dialog element */
function (results) {
var passed = false;
try {
var element = document.createElement('dialog');
try {
passed = typeof HTMLDialogElement != 'undefined' && element instanceof HTMLDialogElement;
} catch (error) {
}
} catch (error) {
}
results.addItem({
key: 'elements.interactive.dialog',
passed: passed
});
},
/* hidden attribute */
function (results) {
results.addItem({
key: 'elements.hidden',
passed: 'hidden' in document.createElement('div')
});
},
/* outerHTML property */
function (results) {
results.addItem({
key: 'elements.dynamic.outerHTML',
passed: 'outerHTML' in document.createElement('div')
});
},
/* insertAdjacentHTML property */
function (results) {
results.addItem({
key: 'elements.dynamic.insertAdjacentHTML',
passed: 'insertAdjacentHTML' in document.createElement('div')
});
},
/* input type=text */
function (results) {
var element = createInput('text');
results.addItem({
key: 'form.text.element',
passed: element.type == 'text'
});
results.addItem({
key: 'form.text.selection',
passed: 'selectionDirection' in element
});
},
/* input type=search */
function (results) {
var element = createInput('search');
results.addItem({
key: 'form.search.element',
passed: element.type == 'search'
});
},
/* input type=tel */
function (results) {
var element = createInput('tel');
results.addItem({
key: 'form.tel.element',
passed: element.type == 'tel'
});
},
/* input type=url */
function (results) {
var element = createInput('url');
var validation = false;
if ('validity' in element) {
validation = true;
element.value = "foo";
validation &= !element.validity.valid
element.value = "http://foo.org";
validation &= element.validity.valid
}
results.addItem({
key: 'form.url.element',
passed: element.type == 'url'
});
results.addItem({
key: 'form.url.validation',
passed: validation
});
},
/* input type=email */
function (results) {
var element = createInput('email');
var validation = false;
if ('validity' in element) {
validation = true;
element.value = "foo";
validation &= !element.validity.valid
element.value = "foo@bar.org";
validation &= element.validity.valid
}
results.addItem({
key: 'form.email.element',
passed: element.type == 'email'
});
results.addItem({
key: 'form.email.validation',
passed: validation
});
},
/* input type=date, month, week, time, datetime and datetime-local */
function (results) {
var types = ['date', 'month', 'week', 'time', 'datetime', 'datetime-local'];
for (var t = 0; t < types.length; t++) {
var element = createInput(types[t]);
element.value = "foobar";
var sanitization = element.value == '';
var minimal = element.type == types[t];
results.addItem({
key: 'form.' + types[t] + '.element',
passed: minimal
});
results.addItem({
key: 'form.' + types[t] + '.ui',
passed: minimal && sanitization // Testing UI reliably is not possible, so we assume if sanitization is support we also have a UI and use the blacklist to make corrections
});
results.addItem({
key: 'form.' + types[t] + '.sanitization',
passed: minimal && sanitization
});
results.addItem({
key: 'form.' + types[t] + '.min',
passed: minimal && 'min' in element
});
results.addItem({
key: 'form.' + types[t] + '.max',
passed: minimal && 'max' in element
});
results.addItem({
key: 'form.' + types[t] + '.step',
passed: minimal && 'step' in element
});
results.addItem({
key: 'form.' + types[t] + '.stepDown',
passed: minimal && 'stepDown' in element
});
results.addItem({
key: 'form.' + types[t] + '.stepUp',
passed: minimal && 'stepUp' in element
});
if (types[t] != 'datetime-local' && types[t] != 'datetime') {
results.addItem({
key: 'form.' + types[t] + '.valueAsDate',
passed: minimal && 'valueAsDate' in element
});
}
results.addItem({
key: 'form.' + types[t] + '.valueAsNumber',
passed: minimal && 'valueAsNumber' in element
});
}
},
/* input type=number, range */
function (results) {
var types = ['number', 'range'];
for (var t = 0; t < types.length; t++) {
var element = createInput(types[t]);
element.value = "foobar";
var sanitization = element.value != 'foobar';
var validation = false;
if ('validity' in element) {
validation = true;
element.min = 40;
element.max = 50;
element.value = 100;
validation &= !element.validity.valid
element.value = 42;
validation &= element.validity.valid
}
var minimal = element.type == types[t];
results.addItem({
key: 'form.' + types[t] + '.element',
passed: minimal
});
results.addItem({
key: 'form.' + types[t] + '.ui',
passed: minimal && sanitization // Testing UI reliably is not possible, so we assume if sanitization is support we also have a UI and use the blacklist to make corrections
});
results.addItem({
key: 'form.' + types[t] + '.sanitization',
passed: minimal && sanitization
});
if (types[t] != 'range') {
results.addItem({
key: 'form.' + types[t] + '.validation',
passed: minimal && validation
});
}
results.addItem({
key: 'form.' + types[t] + '.min',
passed: minimal && 'min' in element
});
results.addItem({
key: 'form.' + types[t] + '.max',
passed: minimal && 'max' in element
});
results.addItem({
key: 'form.' + types[t] + '.step',
passed: minimal && 'step' in element
});
results.addItem({
key: 'form.' + types[t] + '.stepDown',
passed: minimal && 'stepDown' in element
});
results.addItem({
key: 'form.' + types[t] + '.stepUp',
passed: minimal && 'stepUp' in element
});
results.addItem({
key: 'form.' + types[t] + '.valueAsNumber',
passed: minimal && 'valueAsNumber' in element
});
}
},
/* input type=color */
function (results) {
var element = createInput('color');
element.value = "foobar";
var sanitization = element.value != 'foobar';
results.addItem({
key: 'form.color.element',
passed: element.type == 'color'
});
results.addItem({
key: 'form.color.ui',
passed: sanitization // Testing UI reliably is not possible, so we assume if sanitization is support we also have a UI and use the blacklist to make corrections
});
results.addItem({
key: 'form.color.sanitization',
passed: sanitization
});
},
/* input type=checkbox */
function (results) {
var element = createInput('checkbox');
results.addItem({
key: 'form.checkbox.element',
passed: element.type == 'checkbox'
});
results.addItem({
key: 'form.checkbox.indeterminate',
passed: 'indeterminate' in element
});
},
/* input type=image */
function (results) {
var element = createInput('image');
element.style.display = 'inline-block';
document.body.appendChild(element);
var supportsWidth = 'width' in element;
var supportsHeight = 'height' in element;
element.setAttribute('width', '100');
element.setAttribute('height', '100');
results.addItem({
key: 'form.image.element',
passed: element.type == 'image'
});
results.addItem({
key: 'form.image.width',
passed: supportsWidth && element.offsetWidth == 100
});
results.addItem({
key: 'form.image.height',
passed: supportsHeight && element.offsetHeight == 100
});
document.body.removeChild(element);
},
/* input type=file */
function (results) {
var element = createInput('file');
results.addItem({
key: 'form.file.element',
passed: element.type == 'file'
});
results.addItem({
key: 'form.file.files',
passed: element.files && element.files instanceof FileList
});
results.addItem({
key: 'form.file.directory',
passed: 'directory' in element && window.Directory
});
},
/* textarea */
function (results) {
var element = document.createElement('textarea');
var passed = false;
try {
passed = typeof HTMLTextAreaElement != 'undefined' && element instanceof HTMLTextAreaElement;
} catch (error) {
}
results.addItem({
key: 'form.textarea.element',
passed: passed
});
results.addItem({
key: 'form.textarea.maxlength',
passed: 'maxLength' in element
});
results.addItem({
key: 'form.textarea.wrap',
passed: 'wrap' in element
});
},
/* select */
function (results) {
var element = document.createElement('select');
var passed = false;
try {
passed = typeof HTMLSelectElement != 'undefined' && element instanceof HTMLSelectElement;
} catch (error) {
}
results.addItem({
key: 'form.select.element',
passed: passed
});
results.addItem({
key: 'form.select.required',
passed: 'required' in element
});
},
/* fieldset */
function (results) {
var element = document.createElement('fieldset');
var passed = false;
try {
passed = typeof HTMLFieldSetElement != 'undefined' && element instanceof HTMLFieldSetElement;
} catch (error) {
}
results.addItem({
key: 'form.fieldset.element',
passed: passed
});
results.addItem({
key: 'form.fieldset.elements',
passed: 'elements' in element
});
results.addItem({
key: 'form.fieldset.disabled',
passed: 'disabled' in element
});
},
/* datalist */
function (results) {
var passed = false;
try {
var element = document.createElement('datalist');
try {
passed = (typeof HTMLDataListElement != 'undefined' && element instanceof HTMLDataListElement) || element.childNodes.length;
} catch (error) {
}
} catch (error) {
}
results.addItem({
key: 'form.datalist.element',
passed: passed
});
var element = document.createElement('input');
results.addItem({
key: 'form.datalist.list',
passed: !!("list" in element)
});
},
/* keygen */
function (results) {
var element = document.createElement('div');
element.innerHTML = '<keygen>';
var passed = false;
try {
passed = typeof HTMLKeygenElement != 'undefined' && element.firstChild instanceof HTMLKeygenElement && 'challenge' in element.firstChild && 'keytype' in element.firstChild;
} catch (error) {
}
results.addItem({
key: 'form.keygen.element',
passed: passed
});
results.addItem({
key: 'form.keygen.challenge',
passed: element.firstChild && 'challenge' in element.firstChild
});
results.addItem({
key: 'form.keygen.keytype',
passed: element.firstChild && 'keytype' in element.firstChild
});
},
/* output */
function (results) {
var passed = false;
try {
var element = document.createElement('output');
try {
passed = typeof HTMLOutputElement != 'undefined' && element instanceof HTMLOutputElement;
} catch (error) {
}
} catch (error) {
}
results.addItem({
key: 'form.output.element',
passed: passed
});
},
/* progress */
function (results) {
var passed = false;
try {
var element = document.createElement('progress');
try {
passed = typeof HTMLProgressElement != 'undefined' && element instanceof HTMLProgressElement;
} catch (error) {
}
} catch (error) {
}
results.addItem({
key: 'form.progress.element',
passed: passed
});
},
/* meter */
function (results) {
var passed = false;
try {
var element = document.createElement('meter');
try {
passed = typeof HTMLMeterElement != 'undefined' && element instanceof HTMLMeterElement;
} catch (error) {
}
} catch (error) {
}
results.addItem({
key: 'form.meter.element',
passed: passed
});
},
/* pattern and required properties */
function (results) {
var element = document.createElement('input');
var props = 'pattern required'.split(' ');
for (var p = 0; p < props.length; p++) {
results.addItem({
key: 'form.validation.' + props[p],
passed: !!(props[p] in element)
});
}
},
/* control property on labels */
function (results) {
var field = document.createElement('input');
field.id = "a";
document.body.appendChild(field);
var label = document.createElement("label");
label.setAttribute('for', 'a');
document.body.appendChild(label);
results.addItem({
key: 'form.association.control',
passed: label.control == field
});
document.body.removeChild(field);
document.body.removeChild(label);
},
/* form attribute on input */
function (results) {
var element = document.createElement('div');
document.body.appendChild(element);
element.innerHTML = '<form id="form"></form><input form="form">';
results.addItem({
key: 'form.association.form',
passed: element.lastChild.form == element.firstChild
});
document.body.removeChild(element);
},
/* formAction, formEnctype, formMethod, formNoValidate and formTarget properties */
function (results) {
var props = 'formAction formEnctype formMethod formNoValidate formTarget'.split(' ');
var element = document.createElement('input');
for (var p = 0; p < props.length; p++) {
results.addItem({
key: 'form.association.' + props[p],
passed: !!(props[p] in element)
});
}
},
/* labels property on input */
function (results) {
var element = document.createElement('input');
document.body.appendChild(element);
element.id = "testFormInput";
var label = document.createElement("label");
label.setAttribute('for', 'testFormInput');
document.body.appendChild(label);
results.addItem({
key: 'form.association.labels',
passed: (!!element.labels && element.labels.length == 1 && element.labels[0] == label)
});
document.body.removeChild(label);
document.body.removeChild(element);
},
/* autofocus */
function (results) {
var element = document.createElement('input');
results.addItem({
key: 'form.other.autofocus',
passed: !!('autofocus' in element)
});
},
/* autocomplete, placeholder, multiple and dirName properties */
function (results) {
var element = document.createElement('input');
var props = 'autocomplete placeholder multiple dirName'.split(' ');
for (var p = 0; p < props.length; p++) {
var prop = props[p].toLowerCase();
results.addItem({
key: 'form.other.' + prop,
passed: !!(props[p] in element)
});
}
},
/* valid, invalid, optional, required, in-range, out-of-range, read-write and read-only css selectors */
function (results) {
var selectors = "valid invalid optional required in-range out-of-range read-write read-only".split(" ");
var passed = [NO | UNKNOWN, NO | UNKNOWN, NO | UNKNOWN, NO | UNKNOWN, NO | UNKNOWN, NO | UNKNOWN, NO | UNKNOWN, NO | UNKNOWN];
/* At this time we are not testing enabled, disabled, checked and indeterminate,
because these selectors are part of the CSS 3 Selector specification and
universally implemented, see http://www.css3.info/selectors-test/
*/
if ('querySelector' in document) {
var element = document.createElement('input');
element.id = 'testFormInput';
element.setAttribute("type", "text");
document.body.appendChild(element);
try {
passed[0] = !!document.querySelector("#testFormInput:valid");
} catch (e) {
passed[0] = NO;
}
try {
passed[6] = !!document.querySelector("#testFormInput:read-write");
} catch (e) {
passed[6] = NO;
try {
passed[6] = document.querySelector("#testFormInput:-moz-read-write") ? YES | PREFIX : NO;
} catch (e) {
}
}
if ("validity" in element && "setCustomValidity" in element) {
element.setCustomValidity("foo");
try {
passed[1] = !!document.querySelector("#testFormInput:invalid");
} catch (e) {
passed[1] = NO;
}
} else {
passed[1] = NO;
}
try {
passed[2] = !!document.querySelector("#testFormInput:optional");
} catch (e) {
passed[2] = NO;
}
element.setAttribute("required", "true");
try {
passed[3] = !!document.querySelector("#testFormInput:required");
} catch (e) {
passed[3] = NO;
}
try {
element.setAttribute("type", "number");
element.setAttribute("min", "10");
element.setAttribute("max", "20");
element.setAttribute("value", "15");
passed[4] = !!document.querySelector("#testFormInput:in-range");
} catch (e) {
passed[4] = NO;
}
try {
element.setAttribute("type", "number");
element.setAttribute("min", "10");
element.setAttribute("max", "20");
element.setAttribute("value", "25");
passed[5] = !!document.querySelector("#testFormInput:out-of-range");
} catch (e) {
passed[5] = NO;
}
document.body.removeChild(element);
var element = document.createElement('input');
element.id = 'testFormInput';
element.setAttribute("type", "text");
element.setAttribute("readonly", "readonly");
document.body.appendChild(element);
try {
passed[7] = !!document.querySelector("#testFormInput:read-only");
} catch (e) {
passed[7] = NO;
try {
passed[7] = document.querySelector("#testFormInput:-moz-read-only") ? YES | PREFIX : NO;
} catch (e) {
}
}
document.body.removeChild(element);
}
for (var i = 0; i < selectors.length; i++) {
results.addItem({
key: 'form.selectors.' + selectors[i],
passed: passed[i]
});
}
},
/* oninput, onchange and oninvalid events */
function (results) {
var inputItem = results.addItem({
key: 'form.events.oninput',
passed: isEventSupported('input')
});
var changeItem = results.addItem({
key: 'form.events.onchange',
passed: isEventSupported('change')
});
var invalidItem = results.addItem({
key: 'form.events.oninvalid',
passed: isEventSupported('invalid')
});
try {
inputItem.startBackground();
changeItem.startBackground();
var event = document.createEvent("KeyboardEvent");
if (event.initKeyEvent) {
event.initKeyEvent("keypress", false, true, null, false, false, false, false, null, 65);
var input = document.createElement('input');
input.style.position = 'fixed';
input.style.left = '-500px';
input.style.top = '0px';
document.body.appendChild(input);
input.addEventListener('input', function () {
inputItem.update({
'passed': true
});
inputItem.stopBackground();
}, true);
input.addEventListener('change', function () {
changeItem.update({
'passed': true
});
changeItem.stopBackground();
}, true);
input.focus();
input.dispatchEvent(event);
input.blur();
window.setTimeout(function () {
document.body.removeChild(input);
inputItem.stopBackground();
changeItem.stopBackground();
}, 1000);
} else {
inputItem.stopBackground();
changeItem.stopBackground();
}
} catch (e) {
inputItem.stopBackground();
changeItem.stopBackground();
}
},
/* checkValidity property */
function (results) {
results.addItem({
key: 'form.formvalidation.checkValidity',
passed: 'checkValidity' in document.createElement('form')
});
},
/* noValidate property */
function (results) {
results.addItem({
key: 'form.formvalidation.noValidate',
passed: 'noValidate' in document.createElement('form')
});
},
/* microdata */
function (results) {
var container = document.createElement('div');
container.innerHTML = '<div id="microdataItem" itemscope itemtype="http://example.net/user"><p>My name is <span id="microdataProperty" itemprop="name">Elizabeth</span>.</p></div>';
document.body.appendChild(container);
var item = document.getElementById('microdataItem');
var property = document.getElementById('microdataProperty');
var passed = true;
// Check the element that contains the property
passed = passed && !!('itemValue' in property) && property.itemValue == 'Elizabeth';
// Check the element that is the item
passed = passed && !!('properties' in item) && item.properties['name'][0].itemValue == 'Elizabeth';
// Check the getItems method
if (!!document.getItems) {
var user = document.getItems('http://example.net/user')[0];
passed = passed && user.properties['name'][0].itemValue == 'Elizabeth';
}
document.body.removeChild(container);
results.addItem({
key: 'microdata',
passed: passed
});
},
/* geolocation */
function (results) {
results.addItem({
key: 'location.geolocation',
passed: !!navigator.geolocation
});
},
/* device orientation */
function (results) {
results.addItem({
key: 'location.orientation',
passed: !!window.DeviceOrientationEvent
});
},
/* device motion */
function (results) {
results.addItem({
key: 'location.motion',
passed: !!window.DeviceMotionEvent
});
},
/* fullscreen */
function (results) {
results.addItem({
key: 'output.requestFullScreen',
passed: !!document.documentElement.requestFullscreen ? YES : !!document.documentElement.webkitRequestFullScreen || !!document.documentElement.mozRequestFullScreen || !!document.documentElement.msRequestFullscreen ? YES | PREFIX : NO
});
},
/* notifications */
function (results) {
results.addItem({
key: 'output.notifications',
passed: 'Notification' in window ? YES : 'webkitNotifications' in window || 'mozNotification' in window.navigator || 'oNotification' in window || 'msNotification' in window ? YES | PREFIX : NO
});
},
/* getUserMedia */
function (results) {
results.addItem({
key: 'media.getUserMedia',
passed: !!navigator.mediaDevices && !!navigator.mediaDevices.getUserMedia ? YES : !!navigator.getUserMedia ? YES | OLD : !!navigator.webkitGetUserMedia || !!navigator.mozGetUserMedia || !!navigator.msGetUserMedia || !!navigator.oGetUserMedia ? YES | PREFIX : NO
});
},
/* getDisplayMedia */
function (results) {
results.addItem({
key: 'media.getDisplayMedia',
passed: !!navigator.mediaDevices && !!navigator.mediaDevices.getDisplayMedia ? YES : NO
});
},
/* enumerateDevices */
function (results) {
results.addItem({
key: 'media.enumerateDevices',
passed: !!navigator.mediaDevices && !!navigator.mediaDevices.enumerateDevices ? YES : NO
});
},
/* getGamepads */
function (results) {
results.addItem({
key: 'input.getGamepads',
passed: !!navigator.getGamepads ? YES : !!navigator.webkitGetGamepads || !!navigator.mozGetGamepads || !!navigator.msGetGamepads || !!navigator.oGetGamepads ? YES | PREFIX : NO
});
},
/* pointerLock */
function (results) {
results.addItem({
key: 'input.pointerLock',
passed: 'pointerLockElement' in document ? YES : 'oPointerLockElement' in document || 'msPointerLockElement' in document || 'mozPointerLockElement' in document || 'webkitPointerLockElement' in document ? YES | PREFIX : NO
});
},
/* pointerevents */
function (results) {
results.addItem({
key: 'input.pointerevents',
passed: !!window.PointerEvent ? YES : !!window.webkitPointerEvent || !!window.mozPointerEvent || !!window.msPointerEvent || !!window.oPointerEvent ? YES | PREFIX : NO
});
},
/* beacon */
function (results) {
results.addItem({
key: 'communication.beacon',
passed: 'sendBeacon' in navigator
});
},
/* eventSource */
function (results) {
results.addItem({
key: 'communication.eventSource',
passed: 'EventSource' in window
});
},
/* fetch */
function (results) {
results.addItem({
key: 'communication.fetch',
passed: 'Promise' in window && typeof window.fetch === 'function' && window.fetch('') instanceof Promise
});
},
/* xmlhttprequest upload */
function (results) {
results.addItem({
key: 'communication.xmlhttprequest2.upload',
passed: window.XMLHttpRequest && 'upload' in new XMLHttpRequest()
});
},
/* xmlhttprequest response text */
function (results) {
var item = results.addItem({
key: 'communication.xmlhttprequest2.response.text',
passed: false
});
if (!window.XMLHttpRequest) return;
var xhr = new window.XMLHttpRequest();
if (typeof xhr.responseType == 'undefined') return;
var done = false;
xhr.onreadystatechange = function () {
if (this.readyState == 4 && !done) {
done = true;
passed = false;
try {
passed = !!(this.responseText); // && this.responseText == '<title>&amp;&<</title>');
} catch (e) {
}
item.stopBackground();
item.update({
'passed': passed
});
}
}
try {
item.startBackground();
xhr.open("GET", "/assets/detect.html?" + Math.random().toString(36).substr(2, 5));
xhr.responseType = "text";
xhr.send();
} catch (e) {
item.stopBackground();
}
},
/* xmlhttprequest response document */
function (results) {
var item = results.addItem({
key: 'communication.xmlhttprequest2.response.document',
passed: false
});
if (!window.XMLHttpRequest) return;
var xhr = new window.XMLHttpRequest();
if (typeof xhr.responseType == 'undefined') return;
var done = false;
xhr.onreadystatechange = function () {
if (this.readyState == 4 && !done) {
done = true;
passed = false;
try {
passed = !!(this.responseXML && this.responseXML.title && this.responseXML.title == "&&<");
} catch (e) {
}
item.stopBackground();
item.update({
'passed': passed
});
}
}
try {
item.startBackground();
xhr.open("GET", "/assets/detect.html?" + Math.random().toString(36).substr(2, 5));
xhr.responseType = "document";
xhr.send();
} catch (e) {
item.stopBackground();
}
},
/* xmlhttprequest response array */
function (results) {
var item = results.addItem({
key: 'communication.xmlhttprequest2.response.array',
passed: false
});
if (!window.XMLHttpRequest || !window.ArrayBuffer) return;
var xhr = new window.XMLHttpRequest();
if (typeof xhr.responseType == 'undefined') return;
var done = false;
xhr.onreadystatechange = function () {
if (this.readyState == 4 && !done) {
done = true;
passed = false;
try {
passed = !!(this.response && this.response instanceof ArrayBuffer);
} catch (e) {
}
item.stopBackground();
item.update({
'passed': passed
});
}
}
try {
item.startBackground();
xhr.open("GET", "/assets/detect.html?" + Math.random().toString(36).substr(2, 5));
xhr.responseType = "arraybuffer";
xhr.send();
} catch (e) {
item.stopBackground();
}
},
/* xmlhttprequest response blob */
function (results) {
var item = results.addItem({
key: 'communication.xmlhttprequest2.response.blob',
passed: false
});
if (!window.XMLHttpRequest || !window.Blob) return;
var xhr = new window.XMLHttpRequest();
if (typeof xhr.responseType == 'undefined') return;
var done = false;
xhr.onreadystatechange = function () {
if (this.readyState == 4 && !done) {
done = true;
passed = false;
try {
passed = !!(this.response && this.response instanceof Blob);
} catch (e) {
}
item.stopBackground();
item.update({
'passed': passed
});
}
}
try {
item.startBackground();
xhr.open("GET", "/assets/detect.html?" + Math.random().toString(36).substr(2, 5));
xhr.responseType = "blob";
xhr.send();
} catch (e) {
item.stopBackground();
}
},
/* websockets */
function (results) {
var websocket = window.WebSocket || window.MozWebSocket;
var passed = 'WebSocket' in window ? YES : 'MozWebSocket' in window ? YES | PREFIX : NO;
if (websocket && websocket.CLOSING !== 2) passed |= OLD;
results.addItem({
key: 'communication.websocket.basic',
passed: passed
});
},
/* binary websockets */
function (results) {
var passed = false;
var protocol = 'https:' == location.protocol ? 'wss' : 'ws';
if ("WebSocket" in window) {
if ("binaryType" in WebSocket.prototype) {
passed = true;
}
else {
try {
passed = !!(new WebSocket(protocol + '://.').binaryType);
} catch (e) {
}
}
}
results.addItem({
key: 'communication.websocket.binary',
passed: passed
});
},
/* WebRTC */
function (results) {
results.addItem({
key: 'rtc.webrtc',
passed: !!window.RTCPeerConnection ? YES : !!window.webkitRTCPeerConnection || !!window.mozRTCPeerConnection || !!window.msRTCPeerConnection || !!window.oRTCPeerConnection ? YES | PREFIX : NO
});
},
/* ObjectRTC */
function (results) {
results.addItem({
key: 'rtc.objectrtc',
passed: !!window.RTCIceTransport ? YES : !!window.webkitRTCIceTransport || !!window.mozRTCIceTransport || !!window.msRTCIceTransport || !!window.oRTCIceTransport ? YES | PREFIX : NO
});
},
/* Datachannel */
function (results) {
var passed = false;
try {
o = new (window.RTCPeerConnection || window.msRTCPeerConnection || window.mozRTCPeerConnection || window.webkitRTCPeerConnection)(null);
passed = 'createDataChannel' in o;
}
catch (e) {
}
results.addItem({
key: 'rtc.datachannel',
passed: passed ? (window.RTCPeerConnection ? YES : YES | PREFIX) : NO
});
},
/* MediaRecorder */
function (results) {
results.addItem({
key: 'rtc.recorder',
passed: 'MediaRecorder' in window
});
},
/* Draggable */
function (results) {
results.addItem({
key: 'interaction.dragdrop.attributes.draggable',
passed: 'draggable' in document.createElement('div')
});
},
/* Dropzone */
function (results) {
var element = document.createElement('div');
results.addItem({
key: 'interaction.dragdrop.attributes.dropzone',
passed: 'dropzone' in element ? YES : 'webkitdropzone' in element || 'mozdropzone' in element || 'msdropzone' in element || 'odropzone' in element ? YES | PREFIX : NO
});
},
/* Drag and drop events */
function (results) {
var passed = 'draggable' in document.createElement('div')
/* We need to check if the draggable attribute is supported, because older versions of IE do
support the incompatible versions of the events below. IE 9 and up do support the HTML5
events in combination with the draggable attribute */
results.addItem({
key: 'interaction.dragdrop.events.ondrag',
passed: isEventSupported('drag') && passed
});
results.addItem({
key: 'interaction.dragdrop.events.ondragstart',
passed: isEventSupported('dragstart') && passed
});
results.addItem({
key: 'interaction.dragdrop.events.ondragenter',
passed: isEventSupported('dragenter') && passed
});
results.addItem({
key: 'interaction.dragdrop.events.ondragover',
passed: isEventSupported('dragover') && passed
});
results.addItem({
key: 'interaction.dragdrop.events.ondragleave',
passed: isEventSupported('dragleave') && passed
});
results.addItem({
key: 'interaction.dragdrop.events.ondragend',
passed: isEventSupported('dragend') && passed
});
results.addItem({
key: 'interaction.dragdrop.events.ondrop',
passed: isEventSupported('drop') && passed
});
},
/* contentEditable */
function (results) {
results.addItem({
key: 'interaction.editing.elements.contentEditable',
passed: 'contentEditable' in document.createElement('div')
});
},
/* isContentEditable */
function (results) {
results.addItem({
key: 'interaction.editing.elements.isContentEditable',
passed: 'isContentEditable' in document.createElement('div')
});
},
/* designMode */
function (results) {
results.addItem({
key: 'interaction.editing.documents.designMode',
passed: 'designMode' in document
});
},
/* execCommand and queryCommand API */
function (results) {
results.addItem({
key: 'interaction.editing.apis.execCommand',
passed: 'execCommand' in document
});
results.addItem({
key: 'interaction.editing.apis.queryCommandEnabled',
passed: 'queryCommandEnabled' in document
});
results.addItem({
key: 'interaction.editing.apis.queryCommandIndeterm',
passed: 'queryCommandIndeterm' in document
});
results.addItem({
key: 'interaction.editing.apis.queryCommandState',
passed: 'queryCommandState' in document
});
results.addItem({
key: 'interaction.editing.apis.queryCommandSupported',
passed: 'queryCommandSupported' in document
});
results.addItem({
key: 'interaction.editing.apis.queryCommandValue',
passed: 'queryCommandValue' in document
});
},
/* read-write and read-only selectors */
function (results) {
var selectors = "read-write read-only".split(" ");
var passed = [NO | UNKNOWN, NO | UNKNOWN];
if ('querySelector' in document) {
var element = document.createElement('div');
element.id = 'testDivElement';
element.contentEditable = true;
document.body.appendChild(element);
var nested = document.createElement('div');
nested.id = 'testDivNested';
nested.contentEditable = false;
element.appendChild(nested);
try {
passed[0] = document.querySelector("#testDivElement:read-write") == element;
} catch (e) {
passed[0] = NO;
try {
passed[0] = document.querySelector("#testDivElement:-moz-read-write") == element ? YES | PREFIX : NO;
} catch (e) {
}
}
try {
passed[1] = document.querySelector("#testDivNested:read-only") == nested;
} catch (e) {
passed[1] = NO;
try {
passed[1] = document.querySelector("#testDivNested:-moz-read-only") == nested ? YES | PREFIX : NO;
} catch (e) {
}
}
document.body.removeChild(element);
}
for (var i = 0; i < selectors.length; i++) {
results.addItem({
key: 'interaction.editing.selectors.' + selectors[i],
passed: passed[i]
});
}
},
/* ClipboardEvent */
function (results) {
results.addItem({
key: 'interaction.clipboard',
passed: 'ClipboardEvent' in window
});
},
/* spellcheck */
function (results) {
results.addItem({
key: 'interaction.spellcheck',
passed: 'spellcheck' in document.createElement('div')
});
},
/* webworker */
function (results) {
results.addItem({
key: 'performance.worker',
passed: !!window.Worker
});
},
/* sharedworker */
function (results) {
results.addItem({
key: 'performance.sharedWorker',
passed: !!window.SharedWorker
});
},
/* requestIdleCallback */
function (results) {
results.addItem({
key: 'performance.requestIdleCallback',
passed: 'requestIdleCallback' in window
});
},
/* crypto */
function (results) {
var passed = NO;
try {
var crypto = window.crypto || window.webkitCrypto || window.mozCrypto || window.msCrypto || window.oCrypto;
var available = window.crypto ? YES : window.mozCrypto || window.msCrypto || window.oCrypto ? YES | PREFIX : NO;
passed = !!crypto && 'subtle' in crypto ? available : !!crypto && 'webkitSubtle' in crypto ? YES | PREFIX : NO;
} catch (e) {
}
results.addItem({
key: 'security.crypto',
passed: passed
});
},
/* csp 1.0 */
function (results) {
var passed = false;
if (navigator.webdriver && Browsers.isBrowser('Firefox', '>', 22)) {
passed = YES | DISABLED;
}
var item = results.addItem({
key: 'security.csp10',
passed: passed
});
window.addEventListener('message', function(e) {
if (e.data === 'csp10:passed') {
item.update({
passed: true
});
item.stopBackground();
}
if (e.data === 'csp10:failed') {
item.stopBackground();
}
}, false);
item.startBackground();
var iframe = document.createElement('iframe');
iframe.src = '/assets/csp.html';
iframe.style.visibility = 'hidden';
document.body.appendChild(iframe);
window.setTimeout(function () {
item.stopBackground();
document.body.removeChild(iframe);
}, 1000);
},
/* csp 1.1 */
function (results) {
results.addItem({
key: 'security.csp11',
passed: 'SecurityPolicyViolationEvent' in window
});
},
/* cors */
function (results) {
results.addItem({
key: 'security.cors',
passed: window.XMLHttpRequest && 'withCredentials' in new XMLHttpRequest()
});
},
/* subresource integrity */
function (results) {
results.addItem({
key: 'security.integrity',
passed: 'integrity' in document.createElement('link')
});
},
/* postMessage */
function (results) {
results.addItem({
key: 'security.postMessage',
passed: !!window.postMessage
});
},
/* web authentication */
function (results) {
results.addItem({
key: 'security.authentication',
passed: 'webauthn' in window ? YES : 'msCredentials' in window ? YES | OLD : NO
});
},
/* credential management */
function (results) {
results.addItem({
key: 'security.credential',
passed: 'credentials' in navigator
});
},
/* sandboxed iframe */
function (results) {
results.addItem({
key: 'security.sandbox',
passed: 'sandbox' in document.createElement('iframe')
});
},
/* srcdoc iframe */
function (results) {
results.addItem({
key: 'security.srcdoc',
passed: 'srcdoc' in document.createElement('iframe')
});
},
/* web payments */
function (results) {
results.addItem({
key: 'payments.payments',
passed: 'PaymentRequest' in window
});
},
/* history */
function (results) {
results.addItem({
key: 'other.history',
passed: !!(window.history && history.pushState)
});
},
/* video element */
function (results) {
var element = document.createElement('video');
results.addItem({
key: 'video.element',
passed: !!element.canPlayType
});
/* audioTracks property */
results.addItem({
key: 'video.audiotracks',
passed: 'audioTracks' in element
});
/* videoTracks property */
results.addItem({
key: 'video.videotracks',
passed: 'videoTracks' in element
});
/* subtitles */
results.addItem({
key: 'video.subtitle',
passed: 'track' in document.createElement('track')
});
/* poster */
results.addItem({
key: 'video.poster',
passed: 'poster' in element
});
},
/* video codecs */
function (results) {
var element = document.createElement('video');
/* mpeg-4 codec */
results.addItem({
key: 'video.codecs.mp4.mpeg4',
passed: !!element.canPlayType && canPlayType(element, 'video/mp4; codecs="mp4v.20.8"')
});
/* h.264 codec */
/* I added a workaround for IE9, which only detects H.264 if you also provide an audio codec. Bug filed @ connect.microsoft.com */
results.addItem({
key: 'video.codecs.mp4.h264',
passed: !!element.canPlayType && (canPlayType(element, 'video/mp4; codecs="avc1.42E01E"') || canPlayType(element, 'video/mp4; codecs="avc1.42E01E, mp4a.40.2"'))
});
/* h.265 codec */
results.addItem({
key: 'video.codecs.mp4.h265',
passed: !!element.canPlayType && (canPlayType(element, 'video/mp4; codecs="hvc1.1.L0.0"') || canPlayType(element, 'video/mp4; codecs="hev1.1.L0.0"'))
});
/* theora codec */
results.addItem({
key: 'video.codecs.ogg.theora',
passed: !!element.canPlayType && canPlayType(element, 'video/ogg; codecs="theora"')
});
/* vp8 in webm codec */
results.addItem({
key: 'video.codecs.webm.vp8',
passed: !!element.canPlayType && canPlayType(element, 'video/webm; codecs="vp8"')
});
/* vp9 in webm codec */
results.addItem({
key: 'video.codecs.webm.vp9',
passed: !!element.canPlayType && canPlayType(element, 'video/webm; codecs="vp9"')
});
/* does codec detection work properly? */
var passed = true;
if (!!element.canPlayType) {
if (element.canPlayType('video/nonsense') == 'no') {
passed = false;
log('BUGGY: Codec detection bug in Firefox 3.5.0 - 3.5.1 and Safari 4.0.0 - 4.0.4 that answer "no" to unknown codecs instead of an empty string')
}
if (element.canPlayType('video/webm') == 'probably') {
passed = false;
log('BUGGY: Codec detection bug that Firefox 27 and earlier always says "probably" when asked about WebM, even when the codecs string is not present')
}
if (element.canPlayType('video/mp4; codecs="avc1.42E01E"') == 'maybe' && element.canPlayType('video/mp4') == 'probably') {
passed = false;
log('BUGGY: Codec detection bug in iOS 4.1 and earlier that switches "maybe" and "probably" around')
}
if (element.canPlayType('video/mp4; codecs="avc1.42E01E"') == 'maybe' && element.canPlayType('video/mp4') == 'maybe') {
passed = false;
log('BUGGY: Codec detection bug in Android where no better answer than "maybe" is given')
}
if (element.canPlayType('video/mp4; codecs="avc1.42E01E, mp4a.40.2"') == 'probably' && element.canPlayType('video/mp4; codecs="avc1.42E01E"') != 'probably') {
passed = false;
log('BUGGY: Codec detection bug in Internet Explorer 9 that requires both audio and video codec on test')
}
}
results.addItem({
key: 'video.canplaytype',
passed: element.canPlayType ? (passed ? YES : YES | BUGGY) : NO
});
},
/* audio element */
function (results) {
var element = document.createElement('audio');
results.addItem({
key: 'audio.element',
passed: !!element.canPlayType
});
/* loop property */
results.addItem({
key: 'audio.loop',
passed: 'loop' in element
});
/* preload property */
results.addItem({
key: 'audio.preload',
passed: 'preload' in element
});
},
/* audio codecs */
function (results) {
var element = document.createElement('audio');
/* pcm codec */
results.addItem({
key: 'audio.codecs.pcm',
passed: !!element.canPlayType && canPlayType(element, 'audio/wav; codecs="1"')
});
/* mp3 codec */
var r = false;
if (element.canPlayType) {
var t = element.canPlayType('audio/mpeg');
if (t == 'maybe') {
// We need to check if the browser really supports playing MP3s by loading one and seeing if the
// loadedmetadata event is triggered... but for now assume it does support it...
r = true;
} else if (t == 'probably') {
r = true;
}
}
results.addItem({
key: 'audio.codecs.mp3',
passed: r
});
/* aac codec */
results.addItem({
key: 'audio.codecs.mp4.aac',
passed: !!element.canPlayType && canPlayType(element, 'audio/mp4; codecs="mp4a.40.2"')
});
/* ac3 codec */
results.addItem({
key: 'audio.codecs.mp4.ac3',
passed: !!element.canPlayType && canPlayType(element, 'audio/mp4; codecs="ac-3"')
});
/* enhanced ac3 codec */
results.addItem({
key: 'audio.codecs.mp4.ec3',
passed: !!element.canPlayType && canPlayType(element, 'audio/mp4; codecs="ec-3"')
});
/* ogg vorbis codec */
results.addItem({
key: 'audio.codecs.ogg.vorbis',
passed: !!element.canPlayType && canPlayType(element, 'audio/ogg; codecs="vorbis"')
});
/* ogg opus codec */
results.addItem({
key: 'audio.codecs.ogg.opus',
passed: !!element.canPlayType && canPlayType(element, 'audio/ogg; codecs="opus"')
});
/* webm vorbis codec */
results.addItem({
key: 'audio.codecs.webm.vorbis',
passed: !!element.canPlayType && canPlayType(element, 'audio/webm; codecs="vorbis"')
});
/* webm opus codec */
results.addItem({
key: 'audio.codecs.webm.opus',
passed: !!element.canPlayType && canPlayType(element, 'audio/webm; codecs="opus"')
});
},
/* webaudio */
function (results) {
results.addItem({
key: 'audio.webaudio',
passed: 'AudioContext' in window ? YES : 'webkitAudioContext' in window || 'mozAudioContext' in window || 'oAudioContext' in window || 'msAudioContext' in window ? YES | PREFIX : NO
});
},
/* speech recognition */
function (results) {
results.addItem({
key: 'audio.speechrecognition',
passed: 'SpeechRecognition' in window ? YES : 'webkitSpeechRecognition' in window || 'mozSpeechRecognition' in window || 'oSpeechRecognition' in window || 'msSpeechRecognition' in window ? YES | PREFIX : NO
});
},
/* speech synthesis */
function (results) {
var speechSynthesis = window.speechSynthesis || window.webkitSpeechSynthesis || window.mozSpeechSynthesis || window.oSpeechSynthesis || window.msSpeechSynthesis;
var available = 'speechSynthesis' in window ? YES : 'webkitSpeechSynthesis' in window || 'mozSpeechSynthesis' in window || 'oSpeechSynthesis' in window || 'msSpeechSynthesis' in window ? YES | PREFIX : NO;
var voices = speechSynthesis ? speechSynthesis.getVoices().length : 0;
var speechItem = results.addItem({
key: 'audio.speechsynthesis',
passed: speechSynthesis && voices ? available : NO
});
if (speechSynthesis && !voices) {
if (speechSynthesis.addEventListener) {
speechItem.startBackground();
speechSynthesis.addEventListener("voiceschanged", function () {
voices = speechSynthesis.getVoices().length;
speechItem.update({
passed: voices ? available : NO
});
speechItem.stopBackground();
});
window.setTimeout(function () {
speechItem.stopBackground();
}, 1000);
}
}
},
/* streaming */
function (results) {
var element = document.createElement('video');
/* mediasource */
results.addItem({
key: 'streaming.mediasource',
passed: 'MediaSource' in window ? YES : 'WebKitMediaSource' in window || 'mozMediaSource' in window || 'msMediaSource' in window ? YES | PREFIX : NO
});
/* drm */
results.addItem({
key: 'streaming.drm',
passed: 'setMediaKeys' in element ? YES : 'webkitAddKey' in element || 'webkitSetMediaKeys' in element || 'mozSetMediaKeys' in element || 'msSetMediaKeys' in element ? YES | PREFIX : NO
});
/* dash streaming */
results.addItem({
key: 'streaming.type.dash',
passed: !!element.canPlayType && element.canPlayType('application/dash+xml') != ''
});
/* hls streaming */
results.addItem({
key: 'streaming.type.hls',
passed: !!element.canPlayType && (element.canPlayType('application/vnd.apple.mpegURL') != '' || element.canPlayType('audio/mpegurl') != '')
});
},
/* streaming video codecs */
function (results) {
/* mpeg-4 in mp4 codec */
results.addItem({
key: 'streaming.video.codecs.mp4.mpeg4',
passed: 'MediaSource' in window && MediaSource.isTypeSupported('video/mp4; codecs="mp4v.20.8"')
});
/* h.264 in mp4 codec */
results.addItem({
key: 'streaming.video.codecs.mp4.h264',
passed: 'MediaSource' in window && MediaSource.isTypeSupported('video/mp4; codecs="avc1.42E01E"')
});
/* h.265 in mp4 codec */
results.addItem({
key: 'streaming.video.codecs.mp4.h265',
passed: 'MediaSource' in window && (MediaSource.isTypeSupported('video/mp4; codecs="hvc1.1.L0.0"') || MediaSource.isTypeSupported('video/mp4; codecs="hev1.1.L0.0"'))
});
/* h.264 in ts codec */
results.addItem({
key: 'streaming.video.codecs.ts.h264',
passed: 'MediaSource' in window && MediaSource.isTypeSupported('video/mp2t; codecs="avc1.42E01E"')
});
/* h.265 in ts codec */
results.addItem({
key: 'streaming.video.codecs.ts.h265',
passed: 'MediaSource' in window && (MediaSource.isTypeSupported('video/mp2t; codecs="hvc1.1.L0.0"') || MediaSource.isTypeSupported('video/mp2t; codecs="hev1.1.L0.0"'))
});
/* theora codec */
results.addItem({
key: 'streaming.video.codecs.ogg.theora',
passed: 'MediaSource' in window && MediaSource.isTypeSupported('video/ogg; codecs="theora"')
});
/* vp8 in webm codec */
results.addItem({
key: 'streaming.video.codecs.webm.vp8',
passed: 'MediaSource' in window && MediaSource.isTypeSupported('video/webm; codecs="vp8"')
});
/* vp9 in webm codec */
results.addItem({
key: 'streaming.video.codecs.webm.vp9',
passed: 'MediaSource' in window && MediaSource.isTypeSupported('video/webm; codecs="vp9"')
});
},
/* streaming audio codecs */
function (results) {
/* aac codec in mp4 */
results.addItem({
key: 'streaming.audio.codecs.mp4.aac',
passed: 'MediaSource' in window && MediaSource.isTypeSupported('audio/mp4; codecs="mp4a.40.2"')
});
/* ac3 codec in mp4 */
results.addItem({
key: 'streaming.audio.codecs.mp4.ac3',
passed: 'MediaSource' in window && MediaSource.isTypeSupported('audio/mp4; codecs="ac-3"')
});
/* enhanced ac3 codec in mp4 */
results.addItem({
key: 'streaming.audio.codecs.mp4.ec3',
passed: 'MediaSource' in window && MediaSource.isTypeSupported('audio/mp4; codecs="ec-3"')
});
/* aac codec in mp4 */
results.addItem({
key: 'streaming.audio.codecs.ts.aac',
passed: 'MediaSource' in window && MediaSource.isTypeSupported('audio/mp2t; codecs="mp4a.40.2"')
});
/* ac3 codec in mp4 */
results.addItem({
key: 'streaming.audio.codecs.ts.ac3',
passed: 'MediaSource' in window && MediaSource.isTypeSupported('audio/mp2t; codecs="ac-3"')
});
/* enhanced ac3 codec in mp4 */
results.addItem({
key: 'streaming.audio.codecs.ts.ec3',
passed: 'MediaSource' in window && MediaSource.isTypeSupported('audio/mp2t; codecs="ec-3"')
});
/* vorbis in ogg codec */
results.addItem({
key: 'streaming.audio.codecs.ogg.vorbis',
passed: 'MediaSource' in window && MediaSource.isTypeSupported('audio/ogg; codecs="vorbis"')
});
/* opus in ogg codec */
results.addItem({
key: 'streaming.audio.codecs.ogg.opus',
passed: 'MediaSource' in window && MediaSource.isTypeSupported('audio/ogg; codecs="opus"')
});
/* vorbis in webm codec */
results.addItem({
key: 'streaming.audio.codecs.webm.vorbis',
passed: 'MediaSource' in window && MediaSource.isTypeSupported('audio/webm; codecs="vorbis"')
});
/* opus in webm codec */
results.addItem({
key: 'streaming.audio.codecs.webm.opus',
passed: 'MediaSource' in window && MediaSource.isTypeSupported('audio/webm; codecs="opus"')
});
},
/* picture element */
function (results) {
results.addItem({
key: 'responsive.picture',
passed: 'HTMLPictureElement' in window
});
},
/* srcset attribute */
function (results) {
results.addItem({
key: 'responsive.srcset',
passed: 'srcset' in document.createElement('img')
});
},
/* sizes attribute */
function (results) {
results.addItem({
key: 'responsive.sizes',
passed: 'sizes' in document.createElement('img')
});
},
/* canvas element and 2d context */
function (results) {
var canvas = document.createElement('canvas');
results.addItem({
key: 'canvas.context',
passed: !!(canvas.getContext && typeof CanvasRenderingContext2D != 'undefined' && canvas.getContext('2d') instanceof CanvasRenderingContext2D)
});
},
/* canvas drawing functions */
function (results) {
var canvas = document.createElement('canvas');
/* text support */
var passed = false;
if (canvas.getContext) {
try {
passed = typeof canvas.getContext('2d').fillText == 'function';
}
catch (e) {
}
}
results.addItem({
key: 'canvas.text',
passed: passed
});
/* ellipse support */
var passed = false;
if (canvas.getContext) {
try {
passed = typeof canvas.getContext('2d').ellipse != 'undefined';
}
catch (e) {
}
}
results.addItem({
key: 'canvas.ellipse',
passed: passed
});
/* dashed support */
var passed = false;
if (canvas.getContext) {
try {
passed = typeof canvas.getContext('2d').setLineDash != 'undefined';
}
catch (e) {
}
}
results.addItem({
key: 'canvas.dashed',
passed: passed
});
/* focusring support */
var passed = false;
if (canvas.getContext) {
try {
passed = typeof canvas.getContext('2d').drawFocusIfNeeded != 'undefined';
}
catch (e) {
}
}
results.addItem({
key: 'canvas.focusring',
passed: passed
});
/* hittest support */
var passed = false;
if (canvas.getContext) {
try {
passed = typeof canvas.getContext('2d').addHitRegion != 'undefined';
}
catch (e) {
}
}
results.addItem({
key: 'canvas.hittest',
passed: passed
});
},
/* path support */
function (results) {
results.addItem({
key: 'canvas.path',
passed: typeof Path2D != "undefined" ? YES : typeof Path != "undefined" ? YES | OLD : NO
});
},
/* blending support */
function (results) {
var canvas = document.createElement('canvas');
var passed = false;
if (canvas.getContext) {
canvas.width = 1;
canvas.height = 1;
try {
var ctx = canvas.getContext('2d');
ctx.fillStyle = '#fff';
ctx.fillRect(0, 0, 1, 1);
ctx.globalCompositeOperation = 'screen';
ctx.fillStyle = '#000';
ctx.fillRect(0, 0, 1, 1);
var data = ctx.getImageData(0, 0, 1, 1);
passed = ctx.globalCompositeOperation == 'screen' && data.data[0] == 255;
}
catch (e) {
}
}
results.addItem({
key: 'canvas.blending',
passed: passed
});
},
/* export to image format */
function (results) {
var canvas = document.createElement('canvas');
/* export to png */
var passed = false;
if (canvas.getContext) {
try {
passed = canvas.toDataURL('image/png').substring(5, 14) == 'image/png';
}
catch (e) {
}
}
results.addItem({
key: 'canvas.png',
passed: passed
});
/* export to jpeg */
var passed = false;
if (canvas.getContext) {
try {
passed = canvas.toDataURL('image/jpeg').substring(5, 15) == 'image/jpeg';
}
catch (e) {
}
}
results.addItem({
key: 'canvas.jpeg',
passed: passed
});
/* export to jpeg xr */
var passed = false;
if (canvas.getContext) {
try {
passed = canvas.toDataURL('image/vnd.ms-photo').substring(5, 23) == 'image/vnd.ms-photo';
}
catch (e) {
}
}
results.addItem({
key: 'canvas.jpegxr',
passed: passed
});
/* export to webp */
var passed = false;
if (canvas.getContext) {
try {
passed = canvas.toDataURL('image/webp').substring(5, 15) == 'image/webp';
}
catch (e) {
}
}
results.addItem({
key: 'canvas.webp',
passed: passed
});
},
/* webgl */
function (results) {
var element = document.createElement('canvas');
var passed = 'WebGLRenderingContext' in window;
var contexts = ['webgl', 'experimental-webgl', 'ms-webgl', 'moz-webgl', 'opera-3d', 'webkit-3d', 'ms-3d', '3d'];
var context = '';
var enabled = false;
for (var b = -1, len = contexts.length; ++b < len;) {
try {
if (element.getContext(contexts[b])) {
context = contexts[b];
enabled = true;
break;
};
} catch (e) { }
}
results.addItem({
key: '3d.webgl',
passed: enabled ? (context == 'webgl' ? YES : (context == 'experimental-webgl' ? YES | EXPERIMENTAL : YES | PREFIX)) : (passed ? YES | DISABLED : NO)
});
},
/* webgl2 */
function (results) {
var element = document.createElement('canvas');
var contexts = ['webgl2', 'experimental-webgl2'];
var context = '';
var enabled = false;
var passed = 'WebGL2RenderingContext' in window;
for (var b = -1, len = contexts.length; ++b < len;) {
try {
if (element.getContext(contexts[b])) {
context = contexts[b];
enabled = true;
break;
};
} catch (e) { }
}
results.addItem({
key: '3d.webgl2',
passed: enabled ? (context == 'webgl2' ? YES : (context == 'experimental-webgl2' ? YES | EXPERIMENTAL : YES | PREFIX)) : (passed ? YES | DISABLED : NO)
});
},
/* webvr */
function (results) {
results.addItem({
key: '3d.webvr',
passed: 'getVRDisplays' in navigator ? YES : 'mozGetVRDevices' in navigator ? YES | PREFIX : NO
});
},
/* animation api */
function (results) {
results.addItem({
key: 'animation.webanimation',
passed: 'animate' in document.createElement('div')
});
},
/* requestAnimationFrame */
function (results) {
results.addItem({
key: 'animation.requestAnimationFrame',
passed: !!window.requestAnimationFrame ? YES : !!window.webkitRequestAnimationFrame || !!window.mozRequestAnimationFrame || !!window.msRequestAnimationFrame || !!window.oRequestAnimationFrame ? YES | PREFIX : NO
});
},
/* applicationCache */
function (results) {
results.addItem({
key: 'offline.applicationCache',
passed: !!window.applicationCache
});
},
/* serviceWorker */
function (results) {
results.addItem({
key: 'offline.serviceWorkers',
passed: !!window.navigator.serviceWorker
});
},
/* serviceWorker */
function (results) {
results.addItem({
key: 'offline.pushMessages',
passed: 'PushManager' in window && 'PushSubscription' in window
});
},
/* registerProtocolHandler */
function (results) {
results.addItem({
key: 'offline.registerProtocolHandler',
passed: !!window.navigator.registerProtocolHandler
});
},
/* registerContentHandler */
function (results) {
results.addItem({
key: 'offline.registerContentHandler',
passed: !!window.navigator.registerContentHandler
});
},
/* session storage */
function (results) {
results.addItem({
key: 'storage.sessionStorage',
passed: 'sessionStorage' in window && window.sessionStorage != null
});
},
/* local storage */
function (results) {
var passed = false;
try {
passed = 'localStorage' in window && window.localStorage != null
} catch (e) {
/* If we get a security exception we know the feature exists, but cookies are disabled */
if (e.name == 'NS_ERROR_DOM_SECURITY_ERR' || e.name == 'SecurityError') {
passed = YES | DISABLED;
}
}
results.addItem({
key: 'storage.localStorage',
passed: passed
});
},
/* indexeddb */
function (results) {
var indexedDB;
var passed = false;
try {
indexedDB = window.indexedDB || window.webkitIndexedDB || window.mozIndexedDB || window.moz_indexedDB || window.oIndexedDB || window.msIndexedDB;
passed = !!window.indexedDB ? YES : !!window.webkitIndexedDB || !!window.mozIndexedDB || !!window.moz_indexedDB || !!window.oIndexedDB || !!window.msIndexedDB ? YES | PREFIX : NO;
if (indexedDB && ! 'deleteDatabase' in indexedDB) {
passed |= BUGGY;
log('BUGGY: missing deleteDatabase function on indexedDB');
}
} catch (e) {
/* If we get a security exception we know the feature exists, but cookies are disabled */
if (e.name == 'NS_ERROR_DOM_SECURITY_ERR' || e.name == 'SecurityError') {
passed = YES | DISABLED;
}
}
results.addItem({
key: 'storage.indexedDB.basic',
passed: passed
});
/* indexeddb blob and arraybuffer storage */
var blobitem = results.addItem({
key: 'storage.indexedDB.blob',
passed: passed & DISABLED || passed & BUGGY ? NO | UNKNOWN : NO
});
var arrayitem = results.addItem({
key: 'storage.indexedDB.arraybuffer',
passed: passed & DISABLED || passed & BUGGY ? NO | UNKNOWN : NO
});
if (indexedDB && 'deleteDatabase' in indexedDB) {
try {
blobitem.startBackground();
arrayitem.startBackground();
var request = indexedDB.deleteDatabase('html5test');
request.onerror = function (e) {
blobitem.stopBackground();
arrayitem.stopBackground();
};
request.onsuccess = function () {
var request = indexedDB.open('html5test', 1);
request.onupgradeneeded = function () {
request.result.createObjectStore("store");
};
request.onerror = function (event) {
blobitem.stopBackground();
arrayitem.stopBackground();
};
request.onsuccess = function () {
var db = request.result;
try {
db.transaction("store", "readwrite").objectStore("store").put(new Blob(), "key");
blobitem.update({
passed: true
});
} catch (e) {
}
try {
db.transaction("store", "readwrite").objectStore("store").put(new ArrayBuffer(), "key");
arrayitem.update({
passed: true
});
} catch (e) {
}
blobitem.stopBackground();
arrayitem.stopBackground();
db.close();
indexedDB.deleteDatabase('html5test');
};
};
} catch (e) {
blobitem.stopBackground();
arrayitem.stopBackground();
}
}
},
/* websql */
function (results) {
results.addItem({
key: 'storage.sqlDatabase',
passed: !!window.openDatabase
});
},
/* file reader */
function (results) {
results.addItem({
key: 'files.fileReader',
passed: 'FileReader' in window
});
/* file reader as blob */
results.addItem({
key: 'files.fileReader.blob',
passed: 'Blob' in window
});
/* file reader as data url */
results.addItem({
key: 'files.fileReader.dataURL',
passed: 'FileReader' in window && 'readAsDataURL' in (new FileReader())
});
/* file reader as array buffer */
results.addItem({
key: 'files.fileReader.arraybuffer',
passed: 'FileReader' in window && 'readAsArrayBuffer' in (new FileReader())
});
/* file reader as object url */
results.addItem({
key: 'files.fileReader.objectURL',
passed: 'URL' in window && 'createObjectURL' in URL
});
},
/* request file system */
function (results) {
results.addItem({
key: 'files.fileSystem',
passed: !!window.requestFileSystem ? YES : !!window.webkitRequestFileSystem || !!window.mozRequestFileSystem || !!window.oRequestFileSystem || !!window.msRequestFileSystem ? YES | PREFIX : NO
});
},
/* get file system */
function (results) {
results.addItem({
key: 'files.getFileSystem',
passed: !!navigator.getFileSystem ? YES : !!navigator.webkitGetFileSystem || !!navigator.mozGetFileSystem || !!window.msGetFileSystem ? YES | PREFIX : NO
});
},
/* readable streams */
function (results) {
results.addItem({
key: 'streams.readable',
passed: 'ReadableStream' in window
});
},
/* writeable streams */
function (results) {
results.addItem({
key: 'streams.writeable',
passed: 'WriteableStream' in window
});
},
/* custom elements */
function (results) {
results.addItem({
key: 'components.custom',
passed: 'registerElement' in document
});
},
/* shadow dom */
function (results) {
results.addItem({
key: 'components.shadowdom',
passed: 'attachShadow' in document.createElement('div') ? YES : 'createShadowRoot' in document.createElement('div') || 'webkitCreateShadowRoot' in document.createElement('div') ? YES | OLD : NO
});
},
/* templates */
function (results) {
var passed = false;
try {
passed = 'content' in document.createElement('template');
} catch (error) {
}
results.addItem({
key: 'components.template',
passed: passed
});
},
/* html imports */
function (results) {
results.addItem({
key: 'components.imports',
passed: 'import' in document.createElement('link')
});
},
/* async scripts */
function (results) {
results.addItem({
key: 'scripting.async',
passed: 'async' in document.createElement('script')
});
},
/* deferred scripts */
function (results) {
results.addItem({
key: 'scripting.defer',
passed: 'defer' in document.createElement('script')
});
},
/* script error reporting */
function (results) {
results.addItem({
key: 'scripting.onerror',
passed: isEventSupported('error')
});
},
/* script execution events */
function (results) {
var executionevents = results.addItem({
key: 'scripting.executionevents',
passed: false
});
executionevents.startBackground();
var before = false;
var s = document.createElement('script');
s.src = "data:text/javascript;charset=utf-8,window"
s.addEventListener('beforescriptexecute', function () {
before = true;
}, true);
s.addEventListener('afterscriptexecute', function () {
if (before) {
executionevents.update({
passed: true
});
}
executionevents.stopBackground();
}, true);
document.body.appendChild(s);
window.setTimeout(function () {
executionevents.stopBackground();
}, 500);
},
/* base64 encoding and decoding */
function (results) {
results.addItem({
key: 'scripting.base64',
passed: 'btoa' in window && 'atob' in window
});
},
/* mutation observer */
function (results) {
results.addItem({
key: 'scripting.mutationObserver',
passed: 'MutationObserver' in window ? YES : 'WebKitMutationObserver' in window || 'MozMutationObserver' in window || 'oMutationObserver' in window || 'msMutationObserver' in window ? YES | PREFIX : NO
});
},
/* url api */
function (results) {
results.addItem({
key: 'scripting.url',
passed: 'URL' in window ? YES : 'WebKitURL' in window || 'MozURL' in window || 'oURL' in window || 'msURL' in window ? YES | PREFIX : NO
});
},
/* text encoding api */
function (results) {
results.addItem({
key: 'scripting.encoding',
passed: 'TextEncoder' in window && 'TextDecoder' in window ? YES : NO
});
},
/* json encoding and decoding */
function (results) {
results.addItem({
key: 'scripting.es5.json',
passed: 'JSON' in window && 'parse' in JSON
});
},
/* array functions */
function (results) {
var passed = !!(Array.prototype &&
Array.prototype.every &&
Array.prototype.filter &&
Array.prototype.forEach &&
Array.prototype.indexOf &&
Array.prototype.lastIndexOf &&
Array.prototype.map &&
Array.prototype.some &&
Array.prototype.reduce &&
Array.prototype.reduceRight &&
Array.isArray)
results.addItem({
key: 'scripting.es5.array',
passed: passed ? YES : NO
});
},
/* date functions */
function (results) {
var canParseISODate = false;
try {
canParseISODate = !!Date.parse('2013-04-12T06:06:37.307Z');
} catch (e) {
}
var passed = !!(Date.now &&
Date.prototype &&
Date.prototype.toISOString &&
Date.prototype.toJSON &&
canParseISODate);
results.addItem({
key: 'scripting.es5.date',
passed: passed ? YES : NO
});
},
/* function functions */
function (results) {
var passed = !!(Function.prototype && Function.prototype.bind);
results.addItem({
key: 'scripting.es5.function',
passed: passed ? YES : NO
});
},
/* object functions */
function (results) {
var passed = !!(Object.keys &&
Object.create &&
Object.getPrototypeOf &&
Object.getOwnPropertyNames &&
Object.isSealed &&
Object.isFrozen &&
Object.isExtensible &&
Object.getOwnPropertyDescriptor &&
Object.defineProperty &&
Object.defineProperties &&
Object.seal &&
Object.freeze &&
Object.preventExtensions);
results.addItem({
key: 'scripting.es5.object',
passed: passed ? YES : NO
});
},
/* strict */
function (results) {
var passed = (function() {'use strict'; return !this; })()
results.addItem({
key: 'scripting.es5.strict',
passed: passed ? YES : NO
});
},
/* string functions */
function (results) {
var passed = !!(String.prototype && String.prototype.trim)
results.addItem({
key: 'scripting.es5.string',
passed: passed ? YES : NO
});
},
/* internationalisation api */
function (results) {
results.addItem({
key: 'scripting.es6.i18n',
passed: 'Intl' in window ? YES : NO
});
},
/* promises */
function (results) {
var passed = 'Promise' in window ? YES | OLD : NO;
if ('Promise' in window &&
'resolve' in window.Promise &&
'reject' in window.Promise &&
'all' in window.Promise &&
'race' in window.Promise &&
(function () {
var resolve;
new window.Promise(function (r) { resolve = r; });
return typeof resolve === 'function';
} ())) {
passed = YES;
}
results.addItem({
key: 'scripting.es6.promises',
passed: passed
});
},
/* const */
function (results) {
var passed = YES;
try {
eval('const a = 1');
} catch (e) {
passed = NO;
}
results.addItem({
key: 'scripting.es6.const',
passed: passed
});
},
/* let */
function (results) {
var passed = YES;
try {
eval('let a = 1');
} catch (e) {
passed = NO;
}
results.addItem({
key: 'scripting.es6.let',
passed: passed
});
},
/* arrow functions */
function (results) {
var passed = YES;
try {
eval('()=>{}');
} catch (e) {
passed = NO;
}
results.addItem({
key: 'scripting.es6.arrow',
passed: passed
});
},
/* classes */
function (results) {
var passed = YES;
try {
eval('class Something {}');
} catch (e) {
passed = NO;
}
results.addItem({
key: 'scripting.es6.class',
passed: passed
});
},
/* generators */
function (results) {
var passed = YES;
try {
eval('function* test() {}');
} catch (e) {
passed = NO;
}
results.addItem({
key: 'scripting.es6.generators',
passed: passed
});
},
/* template strings */
function (results) {
var passed = YES;
try {
eval('var a = `a`');
} catch (e) {
passed = NO;
}
results.addItem({
key: 'scripting.es6.template',
passed: passed
});
},
/* destructuring */
function (results) {
var passed = YES;
try {
eval('var { first: f, last: l } = { first: "Jane", last: "Doe" }');
} catch (e) {
passed = NO;
}
results.addItem({
key: 'scripting.es6.destructuring',
passed: passed
});
},
/* spread */
function (results) {
var passed = YES;
try {
eval('Math.max(...[ 5, 10 ])');
} catch (e) {
passed = NO;
}
results.addItem({
key: 'scripting.es6.spread',
passed: passed
});
},
/* default params */
function (results) {
var passed = YES;
try {
eval('function test (one = 1) {}');
} catch (e) {
passed = NO;
}
results.addItem({
key: 'scripting.es6.defaultparams',
passed: passed
});
},
/* symbols */
function (results) {
results.addItem({
key: 'scripting.es6.symbol',
passed: typeof Symbol !== 'undefined' ? YES : NO
});
},
/* collections */
function (results) {
var passed = typeof Map !== 'undefined' &&
typeof WeakMap !== 'undefined' &&
typeof Set !== 'undefined' &&
typeof WeakSet !== 'undefined';
results.addItem({
key: 'scripting.es6.collections',
passed: passed ? YES : NO
});
},
/* array functions */
function (results) {
var passed = typeof Array.prototype.find !== 'undefined' &&
typeof Array.prototype.findIndex !== 'undefined' &&
typeof Array.from !== 'undefined' &&
typeof Array.of !== 'undefined' &&
typeof Array.prototype.entries !== 'undefined' &&
typeof Array.prototype.keys !== 'undefined' &&
typeof Array.prototype.copyWithin !== 'undefined' &&
typeof Array.prototype.fill !== 'undefined';
results.addItem({
key: 'scripting.es6.array',
passed: passed ? YES : NO
});
},
/* string functions */
function (results) {
var passed = typeof String.fromCodePoint !== 'undefined' &&
typeof String.raw !== 'undefined' &&
typeof String.prototype.codePointAt !== 'undefined' &&
typeof String.prototype.repeat !== 'undefined' &&
typeof String.prototype.startsWith !== 'undefined' &&
typeof String.prototype.endsWith !== 'undefined' &&
typeof String.prototype.includes !== 'undefined';
results.addItem({
key: 'scripting.es6.string',
passed: passed ? YES : NO
});
},
/* number functions */
function (results) {
var passed = !!(Number.isFinite &&
Number.isInteger &&
Number.isSafeInteger &&
Number.isNaN &&
Number.parseInt &&
Number.parseFloat &&
Number.isInteger(Number.MAX_SAFE_INTEGER) &&
Number.isInteger(Number.MIN_SAFE_INTEGER) &&
Number.isFinite(Number.EPSILON));
results.addItem({
key: 'scripting.es6.number',
passed: passed ? YES : NO
});
},
/* object functions */
function (results) {
var passed = !!(Object.assign &&
Object.is &&
Object.setPrototypeOf);
results.addItem({
key: 'scripting.es6.object',
passed: passed ? YES : NO
});
},
/* math functions */
function (results) {
var passed = !!(Math &&
Math.clz32 &&
Math.cbrt &&
Math.imul &&
Math.sign &&
Math.log10 &&
Math.log2 &&
Math.log1p &&
Math.expm1 &&
Math.cosh &&
Math.sinh &&
Math.tanh &&
Math.acosh &&
Math.asinh &&
Math.atanh &&
Math.hypot &&
Math.trunc &&
Math.fround);
results.addItem({
key: 'scripting.es6.math',
passed: passed ? YES : NO
});
},
/* datatypes */
function (results) {
results.addItem({
key: 'scripting.es6.datatypes.ArrayBuffer',
passed: typeof ArrayBuffer != 'undefined'
});
results.addItem({
key: 'scripting.es6.datatypes.Int8Array',
passed: typeof Int8Array != 'undefined'
});
results.addItem({
key: 'scripting.es6.datatypes.Uint8Array',
passed: typeof Uint8Array != 'undefined'
});
results.addItem({
key: 'scripting.es6.datatypes.Uint8ClampedArray',
passed: typeof Uint8ClampedArray != 'undefined'
});
results.addItem({
key: 'scripting.es6.datatypes.Int16Array',
passed: typeof Int16Array != 'undefined'
});
results.addItem({
key: 'scripting.es6.datatypes.Uint16Array',
passed: typeof Uint16Array != 'undefined'
});
results.addItem({
key: 'scripting.es6.datatypes.Int32Array',
passed: typeof Int32Array != 'undefined'
});
results.addItem({
key: 'scripting.es6.datatypes.Uint32Array',
passed: typeof Uint32Array != 'undefined'
});
results.addItem({
key: 'scripting.es6.datatypes.Float32Array',
passed: typeof Float32Array != 'undefined'
});
results.addItem({
key: 'scripting.es6.datatypes.Float64Array',
passed: typeof Float64Array != 'undefined'
});
results.addItem({
key: 'scripting.es6.datatypes.DataView',
passed: typeof DataView != 'undefined'
});
var passed = typeof ArrayBuffer != 'undefined' &&
typeof Int8Array != 'undefined' &&
typeof Uint8Array != 'undefined' &&
typeof Uint8ClampedArray != 'undefined' &&
typeof Int16Array != 'undefined' &&
typeof Uint16Array != 'undefined' &&
typeof Int32Array != 'undefined' &&
typeof Uint32Array != 'undefined' &&
typeof Float32Array != 'undefined' &&
typeof Float64Array != 'undefined' &&
typeof DataView != 'undefined';
results.addItem({
key: 'scripting.es6.datatypes',
passed: passed ? YES : NO
});
},
/* modules */
function (results) {
var item = results.addItem({
key: 'scripting.es6.modules',
passed: false
});
item.startBackground();
var callback = item.getGlobalCallback(function(scoped) {
item.update({
passed: scoped ? YES : YES | BUGGY
});
if (!scoped) {
log('BUGGY: Non-exported variables are not scoped to the ES6 module');
}
item.stopBackground();
});
var s = document.createElement('script');
s.type = 'module';
s.src = "data:text/javascript;charset=utf-8,var test_module_scope = true; window." + callback + "(typeof window.test_module_scope === 'undefined')";
document.body.appendChild(s);
window.setTimeout(function () {
item.stopBackground();
}, 500);
},
/* async await api */
function (results) {
var passed = YES;
try {
eval('async function a() { return await Promise.resolve() }');
} catch (e) {
passed = NO;
}
results.addItem({
key: 'scripting.es7.async',
passed: passed
});
},
/* page visiblity */
function (results) {
results.addItem({
key: 'other.pagevisiblity',
passed: 'visibilityState' in document ? YES : 'webkitVisibilityState' in document || 'mozVisibilityState' in document || 'oVisibilityState' in document || 'msVisibilityState' in document ? YES | PREFIX : NO
});
},
/* selection */
function (results) {
results.addItem({
key: 'other.getSelection',
passed: !!window.getSelection
});
},
/* scrollIntoView */
function (results) {
results.addItem({
key: 'other.scrollIntoView',
passed: 'scrollIntoView' in document.createElement('div')
});
}
];
/* Helper functions */
var isEventSupported = (function () {
var TAGNAMES = {
'select': 'input', 'change': 'input', 'input': 'input',
'submit': 'form', 'reset': 'form', 'forminput': 'form', 'formchange': 'form',
'error': 'img', 'load': 'img', 'abort': 'img'
}
function isEventSupported(eventName, element) {
element = element || document.createElement(TAGNAMES[eventName] || 'div');
eventName = 'on' + eventName;
var isSupported = (eventName in element);
if (!isSupported) {
if (!element.setAttribute) {
element = document.createElement('div');
}
if (element.setAttribute && element.removeAttribute) {
element.setAttribute(eventName, '');
isSupported = typeof element[eventName] == 'function';
if (typeof element[eventName] != 'undefined') {
element[eventName] = void 0;
}
element.removeAttribute(eventName);
}
}
element = null;
return isSupported;
}
return isEventSupported;
})();
var log = function () {
if (typeof console != 'undefined') {
console.log.apply(console, arguments);
}
};
var createInput = function (type) {
var field = document.createElement('input');
try {
field.setAttribute('type', type);
} catch (e) {
}
return field;
};
var canPlayType = function (element, type) {
/*
There is a bug in iOS 4.1 or earlier where probably and maybe are switched around.
This bug was reported and fixed in iOS 4.2
*/
if (Browsers.isOs('iOS', '<', '4.2'))
return element.canPlayType(type) == 'probably' || element.canPlayType(type) == 'maybe';
else
return element.canPlayType(type) == 'probably';
};
var closesImplicitly = function (name) {
var foo = document.createElement('div');
foo.innerHTML = '<p><' + name + '></' + name + '>';
return foo.childNodes.length == 2;
};
var getStyle = function (element, name) {
function camelCase(str) {
return str.replace(/-\D/g, function (match) {
return match.charAt(1).toUpperCase()
})
}
if (element.style[name]) {
return element.style[name];
} else if (element.currentStyle) {
return element.currentStyle[camelCase(name)];
}
else if (document.defaultView && document.defaultView.getComputedStyle) {
s = document.defaultView.getComputedStyle(element, "");
return s && s.getPropertyValue(name);
} else {
return null;
}
};
var isBlock = function (element) {
return getStyle(element, 'display') == 'block';
};
var isHidden = function (element) {
return getStyle(element, 'display') == 'none';
};
/* Classes */
function List(parent) { this.initialize(parent); }
List.prototype = {
initialize: function (parent) {
this.parent = parent;
this.items = [];
},
addItem: function (data) {
var i = new Item(this, data);
this.items.push(i);
return i;
},
toString: function () {
var value = [];
for (var i = 0; i < this.items.length; i++) {
if (typeof this.items[i].data.passed != 'undefined') value.push(this.items[i].data.key + '=' + (+this.items[i].data.passed));
}
return value.join(',');
}
};
function Item(list, data) { this.initialize(list, data); }
Item.prototype = {
initialize: function (list, data) {
this.list = list;
this.data = data;
if (typeof this.data.passed == 'undefined') this.data.passed = false;
if (this.data.passed) {
var blacklist = this.isOnBlacklist();
if (blacklist) {
this.data.passed = blacklist;
}
}
},
update: function (data) {
for (var key in data) {
this.data[key] = data[key];
}
if (typeof this.data.passed == 'undefined') this.data.passed = false;
if (this.data.passed) {
var blacklist = this.isOnBlacklist();
if (blacklist) {
this.data.passed = blacklist;
}
}
},
isOnBlacklist: function () {
var part = '';
var parts = this.data.key.replace(/\-/g, '.').split('.');
for (var i = 0; i < parts.length; i++) {
part += (i == 0 ? '' : '.') + parts[i];
for (var k = 0; k < blacklists.length; k++) {
if (typeof blacklists[k][1][part] != 'undefined') {
if (blacklists[k][1][part]) {
log('BLOCKED: ' + part + ' is on the blacklist for this browser!');
return blacklists[k][0];
}
}
}
}
return false;
},
startBackground: function () {
this.list.parent.startBackground(this.data.key);
},
stopBackground: function () {
this.list.parent.stopBackground(this.data.key);
},
getGlobalCallback: function(callback) {
var uniqueid = (((1 + Math.random()) * 0x1000000) | 0).toString(16).substring(1);
var that = this;
window['callback_' + uniqueid] = function() {
callback.apply(that, arguments);
};
return 'callback_' + uniqueid;
}
};
function Runner(callback, error) { this.initialize(callback, error); }
Runner.prototype = {
initialize: function (callback, error) {
blacklists = [
[
BLOCKED,
{
'form.file': Browsers.isDevice('Xbox 360') || Browsers.isDevice('Xbox One') || Browsers.isDevice('Playstation 4') || Browsers.isOs('Windows Phone', '<', '8.1') || Browsers.isOs('iOS', '<', '6') || Browsers.isOs('Android', '<', '2.2'),
'form.date.ui': Browsers.isBrowser('Sogou Explorer') || Browsers.isBrowser('Maxthon', '<', '4.0.5') || (Browsers.isBrowser('UC Browser', '<', '8.6') && Browsers.isType('mobile', 'tablet')),
'form.month.ui': Browsers.isBrowser('Sogou Explorer') || Browsers.isBrowser('Maxthon', '<', '4.0.5') || (Browsers.isBrowser('UC Browser', '<', '8.6') && Browsers.isType('mobile', 'tablet')),
'form.week.ui': Browsers.isBrowser('Sogou Explorer') || Browsers.isBrowser('Maxthon', '<', '4.0.5') || (Browsers.isBrowser('UC Browser', '<', '8.6') && Browsers.isType('mobile', 'tablet')),
'form.time.ui': Browsers.isBrowser('Sogou Explorer') || Browsers.isBrowser('Maxthon', '<', '4.0.5') || (Browsers.isBrowser('UC Browser', '<', '8.6') && Browsers.isType('mobile', 'tablet')),
'form.datetime-local.ui': Browsers.isBrowser('Sogou Explorer') || Browsers.isBrowser('Maxthon', '<', '4.0.5') || (Browsers.isBrowser('UC Browser', '<', '8.6') && Browsers.isType('mobile', 'tablet')),
'form.color.ui': Browsers.isBrowser('Sogou Explorer') || (Browsers.isBrowser('UC Browser', '<', '9.8') && Browsers.isType('mobile', 'tablet')),
'form.range.ui': (Browsers.isBrowser('UC Browser', '<', '9.8') && Browsers.isType('mobile', 'tablet')),
'form.progress.element': Browsers.isBrowser('Baidu Browser'),
'files.fileSystem': Browsers.isOs('BlackBerry Tablet OS'),
'input.getUserMedia': Browsers.isDevice('webOS TV') || Browsers.isBrowser('Baidu Browser') || Browsers.isBrowser('Sogou Explorer') || (Browsers.isBrowser('UC Browser', '<', '9.8') && Browsers.isType('mobile', 'tablet')) || Browsers.isBrowser('Dolphin') || Browsers.isBrowser('Safari', '=', '9'),
'input.getGamepads': Browsers.isDevice('webOS TV') || Browsers.isDevice('Playstation 4') || Browsers.isDevice('Wii U'),
'location.geolocation': Browsers.isDevice('webOS TV') || Browsers.isDevice('Xbox One') || Browsers.isBrowser('Baidu Browser') || Browsers.isOs('Google TV'),
'location.orientation': Browsers.isBrowser('Baidu Browser'),
'output.notifications': Browsers.isBrowser('Opera', '=', '18') || Browsers.isBrowser('Baidu Browser') || Browsers.isBrowser('Sogou Explorer'),
'output.requestFullScreen': Browsers.isBrowser('Sogou Explorer') || Browsers.isOs('BlackBerry Tablet OS') || Browsers.isOs('BlackBerry OS'),
'video.subtitle': Browsers.isBrowser('Baidu Browser') || Browsers.isBrowser('Sogou Explorer'),
'3d.webgl': Browsers.isBrowser('Baidu Browser')
}
],
[
DISABLED,
{
'elements.semantic.ping': Browsers.isBrowser('Firefox') || Browsers.isBrowser('Firefox Mobile')
}
],
[
UNCONFIRMED,
{
'interaction.dragdrop': !(Browsers.isType('desktop') ||
Browsers.isType('mobile', 'tablet', 'media') && (
Browsers.isBrowser('Opera') && Browsers.isEngine('Presto')
) ||
Browsers.isType('television') && (
Browsers.isDevice('webOS TV')
)
),
'interaction.editing': !(Browsers.isType('desktop') ||
Browsers.isType('mobile', 'tablet', 'media') && (
Browsers.isOs('iOS', '>=', '5') ||
Browsers.isOs('Android', '>=', '4') ||
Browsers.isOs('Windows Phone', '>=', '7.5') ||
Browsers.isOs('BlackBerry') ||
Browsers.isOs('BlackBerry OS') ||
Browsers.isOs('BlackBerry Tablet OS') ||
Browsers.isOs('Meego') ||
Browsers.isOs('Tizen') ||
Browsers.isEngine('Gecko') ||
Browsers.isEngine('Presto') ||
Browsers.isBrowser('Chrome') ||
Browsers.isBrowser('Polaris', '>=', '8')
) ||
Browsers.isType('television') && (
Browsers.isOs('Tizen') ||
Browsers.isDevice('webOS TV') ||
Browsers.isBrowser('Espial') ||
Browsers.isBrowser('MachBlue XT') ||
Browsers.isEngine('Presto', '>=', '2.9')
) ||
Browsers.isType('gaming') && (
Browsers.isDevice('Xbox 360') ||
Browsers.isDevice('Xbox One') ||
Browsers.isDevice('Playstation 4')
)
)
}
]
];
try {
this.backgroundTasks = [];
this.backgroundIds = {};
this.backgroundId = 0;
this.callback = callback;
this.list = new List(this);
for (var s = 0; s < testsuite.length; s++) {
testsuite[s](this.list);
}
this.waitForBackground();
}
catch (e) {
error(e);
}
},
waitForBackground: function () {
var that = this;
window.setTimeout(function () {
that.checkForBackground.call(that);
}, 300);
},
checkForBackground: function () {
var running = 0;
for (var task = 0; task < this.backgroundTasks.length; task++) { running += this.backgroundTasks[task] }
if (running) {
this.waitForBackground();
} else {
this.finished();
}
},
startBackground: function (id) {
var i = this.backgroundId++;
this.backgroundIds[id] = i;
this.backgroundTasks[i] = 1;
},
stopBackground: function (id) {
this.backgroundTasks[this.backgroundIds[id]] = 0;
},
finished: function () {
var uniqueid = (((1 + Math.random()) * 0x1000000) | 0).toString(16).substring(1) + ("0000000000" + (new Date().getTime() - new Date(2010, 0, 1).getTime()).toString(16)).slice(-10);
this.callback({
release: release,
uniqueid: uniqueid,
results: this.list.toString()
});
}
};
return Runner;
})();