27
key: 'parsing.doctype',
28
passed: document.compatMode == 'CSS1Compat'
37
var e = document.createElement('div');
40
e.innerHTML = "<div<div>";
41
result &= e.firstChild && e.firstChild.nodeName == "DIV<DIV";
43
e.innerHTML = "<div foo<bar=''>";
44
result &= e.firstChild.attributes[0].nodeName == "foo<bar" || e.firstChild.attributes[0].name == "foo<bar";
46
e.innerHTML = "<div foo=`bar`>";
47
result &= e.firstChild.getAttribute("foo") == "`bar`";
49
e.innerHTML = "<div \"foo=''>";
50
result &= e.firstChild && (e.firstChild.attributes[0].nodeName == "\"foo" || e.firstChild.attributes[0].name == "\"foo");
52
e.innerHTML = "<a href='\nbar'></a>";
53
result &= e.firstChild && e.firstChild.getAttribute("href") == "\nbar";
55
e.innerHTML = "<!DOCTYPE html>";
56
result &= e.firstChild == null;
58
e.innerHTML = "\u000D";
59
result &= e.firstChild && e.firstChild.nodeValue == "\u000A";
61
e.innerHTML = "⟨⟩";
62
result &= e.firstChild.nodeValue == "\u27E8\u27E9";
64
e.innerHTML = "'";
65
result &= e.firstChild.nodeValue == "'";
67
e.innerHTML = "ⅈ";
68
result &= e.firstChild.nodeValue == "\u2148";
70
e.innerHTML = "𝕂";
71
result &= e.firstChild.nodeValue == "\uD835\uDD42";
73
e.innerHTML = "∉";
74
result &= e.firstChild.nodeValue == "\u2209";
76
e.innerHTML = '<?import namespace="foo" implementation="#bar">';
77
result &= e.firstChild && e.firstChild.nodeType == 8 && e.firstChild.nodeValue == '?import namespace="foo" implementation="#bar"';
79
e.innerHTML = '<!--foo--bar-->';
80
result &= e.firstChild && e.firstChild.nodeType == 8 && e.firstChild.nodeValue == 'foo--bar';
82
e.innerHTML = '<![CDATA[x]]>';
83
result &= e.firstChild && e.firstChild.nodeType == 8 && e.firstChild.nodeValue == '[CDATA[x]]';
85
e.innerHTML = "<textarea><!--</textarea>--></textarea>";
86
result &= e.firstChild && e.firstChild.firstChild && e.firstChild.firstChild.nodeValue == "<!--";
88
e.innerHTML = "<textarea><!--</textarea>-->";
89
result &= e.firstChild && e.firstChild.firstChild && e.firstChild.firstChild.nodeValue == "<!--";
91
e.innerHTML = "<style><!--</style>--></style>";
92
result &= e.firstChild && e.firstChild.firstChild && e.firstChild.firstChild.nodeValue == "<!--";
94
e.innerHTML = "<style><!--</style>-->";
95
result &= e.firstChild && e.firstChild.firstChild && e.firstChild.firstChild.nodeValue == "<!--";
101
key: 'parsing.tokenizer',
111
var e = document.createElement('div');
114
var h = document.createElement("html");
116
result &= h.firstChild && h.firstChild.nodeName == "HEAD" && h.lastChild.nodeName == "BODY" && h.firstChild.nextSibling == h.lastChild;
122
var t = document.createElement("table");
123
t.innerHTML = "<col>";
124
result &= t.firstChild && t.firstChild.nodeName == "COLGROUP";
129
e.innerHTML = "<ul><li>A </li> <li>B</li></ul>";
130
result &= e.firstChild && e.firstChild.firstChild && e.firstChild.firstChild.firstChild && e.firstChild.firstChild.firstChild.nodeValue == "A ";
132
e.innerHTML = "<table><form><input type=hidden><input></form><div></div></table>";
133
result &= e.firstChild &&
134
e.firstChild.nodeName == "INPUT" &&
135
e.firstChild.nextSibling &&
136
e.firstChild.nextSibling.nodeName == "DIV" &&
137
e.lastChild.nodeName == "TABLE" &&
138
e.firstChild.nextSibling.nextSibling == e.lastChild &&
139
e.lastChild.firstChild &&
140
e.lastChild.firstChild.nodeName == "FORM" &&
141
e.lastChild.firstChild.firstChild == null &&
142
e.lastChild.lastChild.nodeName == "INPUT" &&
143
e.lastChild.firstChild.nextSibling == e.lastChild.lastChild;
145
e.innerHTML = "<i>A<b>B<p></i>C</b>D";
146
result &= e.firstChild &&
147
e.childNodes.length == 3 &&
148
e.childNodes[0].nodeName == "I" &&
149
e.childNodes[0].childNodes.length == 2 &&
150
e.childNodes[0].childNodes[0].nodeValue == "A" &&
151
e.childNodes[0].childNodes[1].nodeName == "B" &&
152
e.childNodes[0].childNodes[1].childNodes.length == 1 &&
153
e.childNodes[0].childNodes[1].childNodes[0].nodeValue == "B" &&
154
e.childNodes[1].nodeName == "B" &&
155
e.childNodes[1].firstChild == null &&
156
e.childNodes[2].nodeName == "P" &&
157
e.childNodes[2].childNodes.length == 2 &&
158
e.childNodes[2].childNodes[0].nodeName == "B" &&
159
e.childNodes[2].childNodes[0].childNodes.length == 2 &&
160
e.childNodes[2].childNodes[0].childNodes[0].nodeName == "I" &&
161
e.childNodes[2].childNodes[0].childNodes[0].firstChild == null &&
162
e.childNodes[2].childNodes[0].childNodes[1].nodeValue == "C" &&
163
e.childNodes[2].childNodes[1].nodeValue == "D";
165
e.innerHTML = "<div></div>";
166
result &= e.firstChild && "namespaceURI" in e.firstChild && e.firstChild.namespaceURI == "http://www.w3.org/1999/xhtml";
178
var e = document.createElement('div');
179
e.innerHTML = '<svg></svg>';
180
var passed = e.firstChild && "namespaceURI" in e.firstChild && e.firstChild.namespaceURI == 'http://www.w3.org/2000/svg';
192
var e = document.createElement('div');
193
e.innerHTML = '<math></math>';
194
var passed = e.firstChild && "namespaceURI" in e.firstChild && e.firstChild.namespaceURI == 'http://www.w3.org/1998/Math/MathML';
197
key: 'parsing.mathml',
205
var element = document.createElement('div');
206
element.setAttribute('data-test', 'test');
209
key: 'elements.dataset',
210
passed: 'dataset' in element
215
/* section, nav, article, header and footer */
218
var elements = 'section nav article aside header footer'.split(' ');
220
for (var e = 0; e < elements.length; e++) {
224
var element = document.createElement(elements[e]);
225
document.body.appendChild(element);
228
passed = element instanceof HTMLElement && !(element instanceof HTMLUnknownElement) && isBlock(element) && closesImplicitly(elements[e]);
232
document.body.removeChild(element);
237
key: 'elements.section.' + elements[e],
246
/* main, figure and figcaption */
249
var elements = 'main figure figcaption'.split(' ');
251
for (var e = 0; e < elements.length; e++) {
255
var element = document.createElement(elements[e]);
256
document.body.appendChild(element);
259
passed = element instanceof HTMLElement && !(element instanceof HTMLUnknownElement) && isBlock(element) && (elements[e] != 'figure' || closesImplicitly(elements[e]));
263
document.body.removeChild(element);
268
key: 'elements.grouping.' + elements[e],
280
key: 'elements.grouping.ol',
281
passed: 'reversed' in document.createElement('ol')
291
key: 'elements.semantic.download',
292
passed: 'download' in document.createElement('a')
301
key: 'elements.semantic.ping',
302
passed: 'ping' in document.createElement('a')
313
var element = document.createElement('mark');
314
document.body.appendChild(element);
317
passed = element instanceof HTMLElement && !(element instanceof HTMLUnknownElement) && (color = getStyle(element, 'background-color')) && (color != 'transparent');
321
document.body.removeChild(element);
326
key: 'elements.semantic.mark',
332
/* ruby, rt, rp element */
335
var container = document.createElement('div');
336
document.body.appendChild(container);
337
container.innerHTML = "<ruby id='ruby'><rp id='rp'></rp><rt id='rt'></rt></ruby>";
338
var rubyElement = document.getElementById('ruby');
339
var rtElement = document.getElementById('rt');
340
var rpElement = document.getElementById('rp');
342
var rubySupport = false;
343
var rtSupport = false;
344
var rpSupport = false;
347
rubySupport = rubyElement && rubyElement instanceof HTMLElement && !(rubyElement instanceof HTMLUnknownElement);
348
rtSupport = rtElement && rtElement instanceof HTMLElement && !(rtElement instanceof HTMLUnknownElement);
349
rpSupport = rpElement && rpElement instanceof HTMLElement && !(rpElement instanceof HTMLUnknownElement) && isHidden(rpElement);
353
document.body.removeChild(container);
356
key: 'elements.semantic.ruby',
357
passed: rubySupport && rtSupport && rpSupport
368
var element = document.createElement('time');
371
passed = typeof HTMLTimeElement != 'undefined' && element instanceof HTMLTimeElement;
378
key: 'elements.semantic.time',
390
var element = document.createElement('data');
393
passed = typeof HTMLDataElement != 'undefined' && element instanceof HTMLDataElement;
400
key: 'elements.semantic.data',
412
var element = document.createElement('wbr');
415
passed = element instanceof HTMLElement && !(element instanceof HTMLUnknownElement);
422
key: 'elements.semantic.wbr',
428
/* details element */
434
var element = document.createElement('details');
435
element.innerHTML = '<summary>a</summary>b';
436
document.body.appendChild(element);
438
var height = element.offsetHeight;
441
passed = height != element.offsetHeight;
443
document.body.removeChild(element);
448
key: 'elements.interactive.details',
454
/* summary element */
460
var element = document.createElement('summary');
461
document.body.appendChild(element);
464
passed = element instanceof HTMLElement && !(element instanceof HTMLUnknownElement);
468
document.body.removeChild(element);
473
key: 'elements.interactive.summary',
482
var passed = legacy = false;
485
var element = document.createElement('menu');
486
document.body.appendChild(element);
489
legacy = typeof HTMLMenuElement != 'undefined' && element instanceof HTMLMenuElement && 'type' in element;
493
// Check default type
494
if (legacy && element.type != 'list') legacy = false;
496
// Check type sanitization
498
element.type = 'foobar';
502
if (legacy && element.type == 'foobar') legacy = false;
504
// Check if correct type sticks
506
element.type = 'list';
511
if (legacy && element.type != 'list') legacy = false;
513
document.body.removeChild(element);
518
var element = document.createElement('menu');
519
document.body.appendChild(element);
522
passed = typeof HTMLMenuElement != 'undefined' && element instanceof HTMLMenuElement && 'type' in element;
526
// Check default type
527
if (passed && element.type != 'toolbar') passed = false;
529
// Check type sanitization
531
element.type = 'foobar';
535
if (passed && element.type == 'foobar') passed = false;
537
// Check if correct type sticks
539
element.type = 'toolbar';
544
if (passed && element.type != 'toolbar') passed = false;
546
document.body.removeChild(element);
551
key: 'elements.interactive.menutoolbar',
552
passed: passed ? YES : legacy ? YES | OLD : NO
560
var passed = legacy = false;
563
var element = document.createElement('menu');
564
document.body.appendChild(element);
567
legacy = typeof HTMLMenuElement != 'undefined' && element instanceof HTMLMenuElement && 'type' in element;
571
// Check if correct type sticks
573
element.type = 'popup';
578
if (legacy && element.type != 'popup') legacy = false;
582
var item = document.createElement('menuitem');
583
element.appendChild(item);
585
if (typeof HTMLMenuItemElement == 'undefined' || !item instanceof HTMLMenuItemElement) legacy = false;
588
document.body.removeChild(element);
593
var element = document.createElement('menu');
594
document.body.appendChild(element);
597
passed = typeof HTMLMenuElement != 'undefined' && element instanceof HTMLMenuElement && 'type' in element;
602
element.type = 'context';
606
// Check default type
607
var second = document.createElement('menu');
608
element.appendChild(second);
609
if (passed && second.type == 'list') legacy = true;
610
if (passed && second.type != 'context') passed = false;
611
element.removeChild(second);
613
// Check type sanitization
615
element.type = 'foobar';
619
if (passed && element.type == 'foobar') passed = false;
621
// Check if correct type sticks
623
element.type = 'context';
628
if (passed && element.type != 'context') passed = false;
632
var item = document.createElement('menuitem');
633
element.appendChild(item);
635
if (typeof HTMLMenuItemElement == 'undefined' || !item instanceof HTMLMenuItemElement) passed = false;
638
document.body.removeChild(element);
643
key: 'elements.interactive.menucontext',
644
passed: passed ? YES : legacy ? YES | OLD : NO
655
var element = document.createElement('dialog');
658
passed = typeof HTMLDialogElement != 'undefined' && element instanceof HTMLDialogElement;
665
key: 'elements.interactive.dialog',
671
/* hidden attribute */
675
key: 'elements.hidden',
676
passed: 'hidden' in document.createElement('div')
681
/* outerHTML property */
685
key: 'elements.dynamic.outerHTML',
686
passed: 'outerHTML' in document.createElement('div')
691
/* insertAdjacentHTML property */
695
key: 'elements.dynamic.insertAdjacentHTML',
696
passed: 'insertAdjacentHTML' in document.createElement('div')
701
/* input type=text */
704
var element = createInput('text');
707
key: 'form.text.element',
708
passed: element.type == 'text'
712
key: 'form.text.selection',
713
passed: 'selectionDirection' in element
718
/* input type=search */
721
var element = createInput('search');
724
key: 'form.search.element',
725
passed: element.type == 'search'
733
var element = createInput('tel');
736
key: 'form.tel.element',
737
passed: element.type == 'tel'
745
var element = createInput('url');
747
var validation = false;
748
if ('validity' in element) {
751
element.value = "foo";
752
validation &= !element.validity.valid
754
element.value = "http://foo.org";
755
validation &= element.validity.valid
759
key: 'form.url.element',
760
passed: element.type == 'url'
764
key: 'form.url.validation',
770
/* input type=email */
773
var element = createInput('email');
775
var validation = false;
776
if ('validity' in element) {
779
element.value = "foo";
780
validation &= !element.validity.valid
782
element.value = "foo@bar.org";
783
validation &= element.validity.valid
787
key: 'form.email.element',
788
passed: element.type == 'email'
792
key: 'form.email.validation',
798
/* input type=date, month, week, time, datetime and datetime-local */
801
var types = ['date', 'month', 'week', 'time', 'datetime', 'datetime-local'];
802
for (var t = 0; t < types.length; t++) {
803
var element = createInput(types[t]);
805
element.value = "foobar";
806
var sanitization = element.value == '';
808
var minimal = element.type == types[t];
811
key: 'form.' + types[t] + '.element',
816
key: 'form.' + types[t] + '.ui',
817
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
821
key: 'form.' + types[t] + '.sanitization',
822
passed: minimal && sanitization
826
key: 'form.' + types[t] + '.min',
827
passed: minimal && 'min' in element
831
key: 'form.' + types[t] + '.max',
832
passed: minimal && 'max' in element
836
key: 'form.' + types[t] + '.step',
837
passed: minimal && 'step' in element
841
key: 'form.' + types[t] + '.stepDown',
842
passed: minimal && 'stepDown' in element
846
key: 'form.' + types[t] + '.stepUp',
847
passed: minimal && 'stepUp' in element
850
if (types[t] != 'datetime-local' && types[t] != 'datetime') {
852
key: 'form.' + types[t] + '.valueAsDate',
853
passed: minimal && 'valueAsDate' in element
858
key: 'form.' + types[t] + '.valueAsNumber',
859
passed: minimal && 'valueAsNumber' in element
865
/* input type=number, range */
868
var types = ['number', 'range'];
869
for (var t = 0; t < types.length; t++) {
870
var element = createInput(types[t]);
872
element.value = "foobar";
873
var sanitization = element.value != 'foobar';
875
var validation = false;
876
if ('validity' in element) {
882
validation &= !element.validity.valid
885
validation &= element.validity.valid
888
var minimal = element.type == types[t];
891
key: 'form.' + types[t] + '.element',
896
key: 'form.' + types[t] + '.ui',
897
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
901
key: 'form.' + types[t] + '.sanitization',
902
passed: minimal && sanitization
905
if (types[t] != 'range') {
907
key: 'form.' + types[t] + '.validation',
908
passed: minimal && validation
913
key: 'form.' + types[t] + '.min',
914
passed: minimal && 'min' in element
918
key: 'form.' + types[t] + '.max',
919
passed: minimal && 'max' in element
923
key: 'form.' + types[t] + '.step',
924
passed: minimal && 'step' in element
928
key: 'form.' + types[t] + '.stepDown',
929
passed: minimal && 'stepDown' in element
933
key: 'form.' + types[t] + '.stepUp',
934
passed: minimal && 'stepUp' in element
938
key: 'form.' + types[t] + '.valueAsNumber',
939
passed: minimal && 'valueAsNumber' in element
945
/* input type=color */
948
var element = createInput('color');
950
element.value = "foobar";
951
var sanitization = element.value != 'foobar';
954
key: 'form.color.element',
955
passed: element.type == 'color'
959
key: 'form.color.ui',
960
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
964
key: 'form.color.sanitization',
970
/* input type=checkbox */
973
var element = createInput('checkbox');
976
key: 'form.checkbox.element',
977
passed: element.type == 'checkbox'
981
key: 'form.checkbox.indeterminate',
982
passed: 'indeterminate' in element
987
/* input type=image */
990
var element = createInput('image');
991
element.style.display = 'inline-block';
992
document.body.appendChild(element);
994
var supportsWidth = 'width' in element;
995
var supportsHeight = 'height' in element;
997
element.setAttribute('width', '100');
998
element.setAttribute('height', '100');
1001
key: 'form.image.element',
1002
passed: element.type == 'image'
1006
key: 'form.image.width',
1007
passed: supportsWidth && element.offsetWidth == 100
1011
key: 'form.image.height',
1012
passed: supportsHeight && element.offsetHeight == 100
1015
document.body.removeChild(element);
1019
/* input type=file */
1021
function (results) {
1022
var element = createInput('file');
1025
key: 'form.file.element',
1026
passed: element.type == 'file'
1030
key: 'form.file.files',
1031
passed: element.files && element.files instanceof FileList
1035
key: 'form.file.directory',
1036
passed: 'directory' in element && window.Directory
1043
function (results) {
1044
var element = document.createElement('textarea');
1048
passed = typeof HTMLTextAreaElement != 'undefined' && element instanceof HTMLTextAreaElement;
1053
key: 'form.textarea.element',
1058
key: 'form.textarea.maxlength',
1059
passed: 'maxLength' in element
1063
key: 'form.textarea.wrap',
1064
passed: 'wrap' in element
1071
function (results) {
1072
var element = document.createElement('select');
1076
passed = typeof HTMLSelectElement != 'undefined' && element instanceof HTMLSelectElement;
1081
key: 'form.select.element',
1086
key: 'form.select.required',
1087
passed: 'required' in element
1094
function (results) {
1095
var element = document.createElement('fieldset');
1099
passed = typeof HTMLFieldSetElement != 'undefined' && element instanceof HTMLFieldSetElement;
1104
key: 'form.fieldset.element',
1109
key: 'form.fieldset.elements',
1110
passed: 'elements' in element
1114
key: 'form.fieldset.disabled',
1115
passed: 'disabled' in element
1122
function (results) {
1126
var element = document.createElement('datalist');
1129
passed = (typeof HTMLDataListElement != 'undefined' && element instanceof HTMLDataListElement) || element.childNodes.length;
1136
key: 'form.datalist.element',
1140
var element = document.createElement('input');
1143
key: 'form.datalist.list',
1144
passed: !!("list" in element)
1151
function (results) {
1152
var element = document.createElement('div');
1153
element.innerHTML = '<keygen>';
1157
passed = typeof HTMLKeygenElement != 'undefined' && element.firstChild instanceof HTMLKeygenElement && 'challenge' in element.firstChild && 'keytype' in element.firstChild;
1162
key: 'form.keygen.element',
1167
key: 'form.keygen.challenge',
1168
passed: element.firstChild && 'challenge' in element.firstChild
1172
key: 'form.keygen.keytype',
1173
passed: element.firstChild && 'keytype' in element.firstChild
1180
function (results) {
1184
var element = document.createElement('output');
1187
passed = typeof HTMLOutputElement != 'undefined' && element instanceof HTMLOutputElement;
1194
key: 'form.output.element',
1202
function (results) {
1206
var element = document.createElement('progress');
1209
passed = typeof HTMLProgressElement != 'undefined' && element instanceof HTMLProgressElement;
1216
key: 'form.progress.element',
1224
function (results) {
1228
var element = document.createElement('meter');
1231
passed = typeof HTMLMeterElement != 'undefined' && element instanceof HTMLMeterElement;
1238
key: 'form.meter.element',
1244
/* pattern and required properties */
1246
function (results) {
1247
var element = document.createElement('input');
1249
var props = 'pattern required'.split(' ');
1251
for (var p = 0; p < props.length; p++) {
1253
key: 'form.validation.' + props[p],
1254
passed: !!(props[p] in element)
1260
/* control property on labels */
1262
function (results) {
1263
var field = document.createElement('input');
1265
document.body.appendChild(field);
1267
var label = document.createElement("label");
1268
label.setAttribute('for', 'a');
1269
document.body.appendChild(label);
1272
key: 'form.association.control',
1273
passed: label.control == field
1276
document.body.removeChild(field);
1277
document.body.removeChild(label);
1281
/* form attribute on input */
1283
function (results) {
1284
var element = document.createElement('div');
1285
document.body.appendChild(element);
1286
element.innerHTML = '<form id="form"></form><input form="form">';
1289
key: 'form.association.form',
1290
passed: element.lastChild.form == element.firstChild
1293
document.body.removeChild(element);
1297
/* formAction, formEnctype, formMethod, formNoValidate and formTarget properties */
1299
function (results) {
1300
var props = 'formAction formEnctype formMethod formNoValidate formTarget'.split(' ');
1302
var element = document.createElement('input');
1304
for (var p = 0; p < props.length; p++) {
1306
key: 'form.association.' + props[p],
1307
passed: !!(props[p] in element)
1313
/* labels property on input */
1315
function (results) {
1316
var element = document.createElement('input');
1317
document.body.appendChild(element);
1318
element.id = "testFormInput";
1320
var label = document.createElement("label");
1321
label.setAttribute('for', 'testFormInput');
1322
document.body.appendChild(label);
1325
key: 'form.association.labels',
1326
passed: (!!element.labels && element.labels.length == 1 && element.labels[0] == label)
1329
document.body.removeChild(label);
1330
document.body.removeChild(element);
1336
function (results) {
1337
var element = document.createElement('input');
1340
key: 'form.other.autofocus',
1341
passed: !!('autofocus' in element)
1346
/* autocomplete, placeholder, multiple and dirName properties */
1348
function (results) {
1349
var element = document.createElement('input');
1351
var props = 'autocomplete placeholder multiple dirName'.split(' ');
1353
for (var p = 0; p < props.length; p++) {
1354
var prop = props[p].toLowerCase();
1356
key: 'form.other.' + prop,
1357
passed: !!(props[p] in element)
1363
/* valid, invalid, optional, required, in-range, out-of-range, read-write and read-only css selectors */
1365
function (results) {
1366
var selectors = "valid invalid optional required in-range out-of-range read-write read-only".split(" ");
1367
var passed = [NO | UNKNOWN, NO | UNKNOWN, NO | UNKNOWN, NO | UNKNOWN, NO | UNKNOWN, NO | UNKNOWN, NO | UNKNOWN, NO | UNKNOWN];
1369
/* At this time we are not testing enabled, disabled, checked and indeterminate,
1370
because these selectors are part of the CSS 3 Selector specification and
1371
universally implemented, see http://www.css3.info/selectors-test/
1374
if ('querySelector' in document) {
1375
var element = document.createElement('input');
1376
element.id = 'testFormInput';
1377
element.setAttribute("type", "text");
1378
document.body.appendChild(element);
1381
passed[0] = !!document.querySelector("#testFormInput:valid");
1387
passed[6] = !!document.querySelector("#testFormInput:read-write");
1392
passed[6] = document.querySelector("#testFormInput:-moz-read-write") ? YES | PREFIX : NO;
1397
if ("validity" in element && "setCustomValidity" in element) {
1398
element.setCustomValidity("foo");
1401
passed[1] = !!document.querySelector("#testFormInput:invalid");
1410
passed[2] = !!document.querySelector("#testFormInput:optional");
1415
element.setAttribute("required", "true");
1418
passed[3] = !!document.querySelector("#testFormInput:required");
1424
element.setAttribute("type", "number");
1425
element.setAttribute("min", "10");
1426
element.setAttribute("max", "20");
1427
element.setAttribute("value", "15");
1428
passed[4] = !!document.querySelector("#testFormInput:in-range");
1435
element.setAttribute("type", "number");
1436
element.setAttribute("min", "10");
1437
element.setAttribute("max", "20");
1438
element.setAttribute("value", "25");
1439
passed[5] = !!document.querySelector("#testFormInput:out-of-range");
1444
document.body.removeChild(element);
1446
var element = document.createElement('input');
1447
element.id = 'testFormInput';
1448
element.setAttribute("type", "text");
1449
element.setAttribute("readonly", "readonly");
1450
document.body.appendChild(element);
1453
passed[7] = !!document.querySelector("#testFormInput:read-only");
1458
passed[7] = document.querySelector("#testFormInput:-moz-read-only") ? YES | PREFIX : NO;
1463
document.body.removeChild(element);
1466
for (var i = 0; i < selectors.length; i++) {
1468
key: 'form.selectors.' + selectors[i],
1475
/* oninput, onchange and oninvalid events */
1477
function (results) {
1478
var inputItem = results.addItem({
1479
key: 'form.events.oninput',
1480
passed: isEventSupported('input')
1483
var changeItem = results.addItem({
1484
key: 'form.events.onchange',
1485
passed: isEventSupported('change')
1488
var invalidItem = results.addItem({
1489
key: 'form.events.oninvalid',
1490
passed: isEventSupported('invalid')
1494
inputItem.startBackground();
1495
changeItem.startBackground();
1497
var event = document.createEvent("KeyboardEvent");
1498
if (event.initKeyEvent) {
1499
event.initKeyEvent("keypress", false, true, null, false, false, false, false, null, 65);
1501
var input = document.createElement('input');
1502
input.style.position = 'fixed';
1503
input.style.left = '-500px';
1504
input.style.top = '0px';
1506
document.body.appendChild(input);
1507
input.addEventListener('input', function () {
1512
inputItem.stopBackground();
1515
input.addEventListener('change', function () {
1520
changeItem.stopBackground();
1524
input.dispatchEvent(event);
1527
window.setTimeout(function () {
1528
document.body.removeChild(input);
1530
inputItem.stopBackground();
1531
changeItem.stopBackground();
1534
inputItem.stopBackground();
1535
changeItem.stopBackground();
1538
inputItem.stopBackground();
1539
changeItem.stopBackground();
1544
/* checkValidity property */
1546
function (results) {
1548
key: 'form.formvalidation.checkValidity',
1549
passed: 'checkValidity' in document.createElement('form')
1554
/* noValidate property */
1556
function (results) {
1558
key: 'form.formvalidation.noValidate',
1559
passed: 'noValidate' in document.createElement('form')
1566
function (results) {
1567
var container = document.createElement('div');
1568
container.innerHTML = '<div id="microdataItem" itemscope itemtype="http://example.net/user"><p>My name is <span id="microdataProperty" itemprop="name">Elizabeth</span>.</p></div>';
1569
document.body.appendChild(container);
1571
var item = document.getElementById('microdataItem');
1572
var property = document.getElementById('microdataProperty');
1575
// Check the element that contains the property
1576
passed = passed && !!('itemValue' in property) && property.itemValue == 'Elizabeth';
1578
// Check the element that is the item
1579
passed = passed && !!('properties' in item) && item.properties['name'][0].itemValue == 'Elizabeth';
1581
// Check the getItems method
1582
if (!!document.getItems) {
1583
var user = document.getItems('http://example.net/user')[0];
1584
passed = passed && user.properties['name'][0].itemValue == 'Elizabeth';
1587
document.body.removeChild(container);
1598
function (results) {
1600
key: 'location.geolocation',
1601
passed: !!navigator.geolocation
1606
/* device orientation */
1608
function (results) {
1610
key: 'location.orientation',
1611
passed: !!window.DeviceOrientationEvent
1618
function (results) {
1620
key: 'location.motion',
1621
passed: !!window.DeviceMotionEvent
1628
function (results) {
1630
key: 'output.requestFullScreen',
1631
passed: !!document.documentElement.requestFullscreen ? YES : !!document.documentElement.webkitRequestFullScreen || !!document.documentElement.mozRequestFullScreen || !!document.documentElement.msRequestFullscreen ? YES | PREFIX : NO
1638
function (results) {
1640
key: 'output.notifications',
1641
passed: 'Notification' in window ? YES : 'webkitNotifications' in window || 'mozNotification' in window.navigator || 'oNotification' in window || 'msNotification' in window ? YES | PREFIX : NO
1648
function (results) {
1650
key: 'media.getUserMedia',
1651
passed: !!navigator.mediaDevices && !!navigator.mediaDevices.getUserMedia ? YES : !!navigator.getUserMedia ? YES | OLD : !!navigator.webkitGetUserMedia || !!navigator.mozGetUserMedia || !!navigator.msGetUserMedia || !!navigator.oGetUserMedia ? YES | PREFIX : NO
1656
/* getDisplayMedia */
1658
function (results) {
1660
key: 'media.getDisplayMedia',
1661
passed: !!navigator.mediaDevices && !!navigator.mediaDevices.getDisplayMedia ? YES : NO
1666
/* enumerateDevices */
1668
function (results) {
1670
key: 'media.enumerateDevices',
1671
passed: !!navigator.mediaDevices && !!navigator.mediaDevices.enumerateDevices ? YES : NO
1678
function (results) {
1680
key: 'input.getGamepads',
1681
passed: !!navigator.getGamepads ? YES : !!navigator.webkitGetGamepads || !!navigator.mozGetGamepads || !!navigator.msGetGamepads || !!navigator.oGetGamepads ? YES | PREFIX : NO
1688
function (results) {
1690
key: 'input.pointerLock',
1691
passed: 'pointerLockElement' in document ? YES : 'oPointerLockElement' in document || 'msPointerLockElement' in document || 'mozPointerLockElement' in document || 'webkitPointerLockElement' in document ? YES | PREFIX : NO
1698
function (results) {
1700
key: 'input.pointerevents',
1701
passed: !!window.PointerEvent ? YES : !!window.webkitPointerEvent || !!window.mozPointerEvent || !!window.msPointerEvent || !!window.oPointerEvent ? YES | PREFIX : NO
1708
function (results) {
1710
key: 'communication.beacon',
1711
passed: 'sendBeacon' in navigator
1718
function (results) {
1720
key: 'communication.eventSource',
1721
passed: 'EventSource' in window
1728
function (results) {
1730
key: 'communication.fetch',
1731
passed: 'Promise' in window && typeof window.fetch === 'function' && window.fetch('') instanceof Promise
1736
/* xmlhttprequest upload */
1738
function (results) {
1740
key: 'communication.xmlhttprequest2.upload',
1741
passed: window.XMLHttpRequest && 'upload' in new XMLHttpRequest()
1746
/* xmlhttprequest response text */
1748
function (results) {
1749
var item = results.addItem({
1750
key: 'communication.xmlhttprequest2.response.text',
1754
if (!window.XMLHttpRequest) return;
1756
var xhr = new window.XMLHttpRequest();
1758
if (typeof xhr.responseType == 'undefined') return;
1762
xhr.onreadystatechange = function () {
1763
if (this.readyState == 4 && !done) {
1768
passed = !!(this.responseText); // && this.responseText == '<title>&&<</title>');
1772
item.stopBackground();
1780
item.startBackground();
1781
xhr.open("GET", "/assets/detect.html?" + Math.random().toString(36).substr(2, 5));
1782
xhr.responseType = "text";
1785
item.stopBackground();
1790
/* xmlhttprequest response document */
1792
function (results) {
1793
var item = results.addItem({
1794
key: 'communication.xmlhttprequest2.response.document',
1798
if (!window.XMLHttpRequest) return;
1800
var xhr = new window.XMLHttpRequest();
1802
if (typeof xhr.responseType == 'undefined') return;
1806
xhr.onreadystatechange = function () {
1807
if (this.readyState == 4 && !done) {
1812
passed = !!(this.responseXML && this.responseXML.title && this.responseXML.title == "&&<");
1816
item.stopBackground();
1824
item.startBackground();
1825
xhr.open("GET", "/assets/detect.html?" + Math.random().toString(36).substr(2, 5));
1826
xhr.responseType = "document";
1829
item.stopBackground();
1834
/* xmlhttprequest response array */
1836
function (results) {
1837
var item = results.addItem({
1838
key: 'communication.xmlhttprequest2.response.array',
1842
if (!window.XMLHttpRequest || !window.ArrayBuffer) return;
1844
var xhr = new window.XMLHttpRequest();
1846
if (typeof xhr.responseType == 'undefined') return;
1850
xhr.onreadystatechange = function () {
1851
if (this.readyState == 4 && !done) {
1856
passed = !!(this.response && this.response instanceof ArrayBuffer);
1860
item.stopBackground();
1868
item.startBackground();
1869
xhr.open("GET", "/assets/detect.html?" + Math.random().toString(36).substr(2, 5));
1870
xhr.responseType = "arraybuffer";
1873
item.stopBackground();
1878
/* xmlhttprequest response blob */
1880
function (results) {
1881
var item = results.addItem({
1882
key: 'communication.xmlhttprequest2.response.blob',
1886
if (!window.XMLHttpRequest || !window.Blob) return;
1888
var xhr = new window.XMLHttpRequest();
1890
if (typeof xhr.responseType == 'undefined') return;
1894
xhr.onreadystatechange = function () {
1895
if (this.readyState == 4 && !done) {
1900
passed = !!(this.response && this.response instanceof Blob);
1904
item.stopBackground();
1912
item.startBackground();
1913
xhr.open("GET", "/assets/detect.html?" + Math.random().toString(36).substr(2, 5));
1914
xhr.responseType = "blob";
1917
item.stopBackground();
1924
function (results) {
1925
var websocket = window.WebSocket || window.MozWebSocket;
1926
var passed = 'WebSocket' in window ? YES : 'MozWebSocket' in window ? YES | PREFIX : NO;
1927
if (websocket && websocket.CLOSING !== 2) passed |= OLD;
1930
key: 'communication.websocket.basic',
1936
/* binary websockets */
1938
function (results) {
1940
var protocol = 'https:' == location.protocol ? 'wss' : 'ws';
1942
if ("WebSocket" in window) {
1943
if ("binaryType" in WebSocket.prototype) {
1948
passed = !!(new WebSocket(protocol + '://.').binaryType);
1955
key: 'communication.websocket.binary',
1963
function (results) {
1966
passed: !!window.RTCPeerConnection ? YES : !!window.webkitRTCPeerConnection || !!window.mozRTCPeerConnection || !!window.msRTCPeerConnection || !!window.oRTCPeerConnection ? YES | PREFIX : NO
1973
function (results) {
1975
key: 'rtc.objectrtc',
1976
passed: !!window.RTCIceTransport ? YES : !!window.webkitRTCIceTransport || !!window.mozRTCIceTransport || !!window.msRTCIceTransport || !!window.oRTCIceTransport ? YES | PREFIX : NO
1983
function (results) {
1986
o = new (window.RTCPeerConnection || window.msRTCPeerConnection || window.mozRTCPeerConnection || window.webkitRTCPeerConnection)(null);
1987
passed = 'createDataChannel' in o;
1993
key: 'rtc.datachannel',
1994
passed: passed ? (window.RTCPeerConnection ? YES : YES | PREFIX) : NO
2001
function (results) {
2003
key: 'rtc.recorder',
2004
passed: 'MediaRecorder' in window
2012
function (results) {
2014
key: 'interaction.dragdrop.attributes.draggable',
2015
passed: 'draggable' in document.createElement('div')
2022
function (results) {
2023
var element = document.createElement('div');
2026
key: 'interaction.dragdrop.attributes.dropzone',
2027
passed: 'dropzone' in element ? YES : 'webkitdropzone' in element || 'mozdropzone' in element || 'msdropzone' in element || 'odropzone' in element ? YES | PREFIX : NO
2032
/* Drag and drop events */
2034
function (results) {
2035
var passed = 'draggable' in document.createElement('div')
2037
/* We need to check if the draggable attribute is supported, because older versions of IE do
2038
support the incompatible versions of the events below. IE 9 and up do support the HTML5
2039
events in combination with the draggable attribute */
2043
key: 'interaction.dragdrop.events.ondrag',
2044
passed: isEventSupported('drag') && passed
2048
key: 'interaction.dragdrop.events.ondragstart',
2049
passed: isEventSupported('dragstart') && passed
2053
key: 'interaction.dragdrop.events.ondragenter',
2054
passed: isEventSupported('dragenter') && passed
2058
key: 'interaction.dragdrop.events.ondragover',
2059
passed: isEventSupported('dragover') && passed
2063
key: 'interaction.dragdrop.events.ondragleave',
2064
passed: isEventSupported('dragleave') && passed
2068
key: 'interaction.dragdrop.events.ondragend',
2069
passed: isEventSupported('dragend') && passed
2073
key: 'interaction.dragdrop.events.ondrop',
2074
passed: isEventSupported('drop') && passed
2079
/* contentEditable */
2081
function (results) {
2083
key: 'interaction.editing.elements.contentEditable',
2084
passed: 'contentEditable' in document.createElement('div')
2089
/* isContentEditable */
2091
function (results) {
2093
key: 'interaction.editing.elements.isContentEditable',
2094
passed: 'isContentEditable' in document.createElement('div')
2101
function (results) {
2103
key: 'interaction.editing.documents.designMode',
2104
passed: 'designMode' in document
2109
/* execCommand and queryCommand API */
2111
function (results) {
2113
key: 'interaction.editing.apis.execCommand',
2114
passed: 'execCommand' in document
2118
key: 'interaction.editing.apis.queryCommandEnabled',
2119
passed: 'queryCommandEnabled' in document
2123
key: 'interaction.editing.apis.queryCommandIndeterm',
2124
passed: 'queryCommandIndeterm' in document
2128
key: 'interaction.editing.apis.queryCommandState',
2129
passed: 'queryCommandState' in document
2133
key: 'interaction.editing.apis.queryCommandSupported',
2134
passed: 'queryCommandSupported' in document
2138
key: 'interaction.editing.apis.queryCommandValue',
2139
passed: 'queryCommandValue' in document
2144
/* read-write and read-only selectors */
2146
function (results) {
2147
var selectors = "read-write read-only".split(" ");
2148
var passed = [NO | UNKNOWN, NO | UNKNOWN];
2150
if ('querySelector' in document) {
2151
var element = document.createElement('div');
2152
element.id = 'testDivElement';
2153
element.contentEditable = true;
2154
document.body.appendChild(element);
2156
var nested = document.createElement('div');
2157
nested.id = 'testDivNested';
2158
nested.contentEditable = false;
2159
element.appendChild(nested);
2162
passed[0] = document.querySelector("#testDivElement:read-write") == element;
2167
passed[0] = document.querySelector("#testDivElement:-moz-read-write") == element ? YES | PREFIX : NO;
2173
passed[1] = document.querySelector("#testDivNested:read-only") == nested;
2178
passed[1] = document.querySelector("#testDivNested:-moz-read-only") == nested ? YES | PREFIX : NO;
2183
document.body.removeChild(element);
2186
for (var i = 0; i < selectors.length; i++) {
2188
key: 'interaction.editing.selectors.' + selectors[i],
2195
/* ClipboardEvent */
2197
function (results) {
2199
key: 'interaction.clipboard',
2200
passed: 'ClipboardEvent' in window
2207
function (results) {
2209
key: 'interaction.spellcheck',
2210
passed: 'spellcheck' in document.createElement('div')
2217
function (results) {
2219
key: 'performance.worker',
2220
passed: !!window.Worker
2227
function (results) {
2229
key: 'performance.sharedWorker',
2230
passed: !!window.SharedWorker
2235
/* requestIdleCallback */
2237
function (results) {
2239
key: 'performance.requestIdleCallback',
2240
passed: 'requestIdleCallback' in window
2247
function (results) {
2250
var crypto = window.crypto || window.webkitCrypto || window.mozCrypto || window.msCrypto || window.oCrypto;
2251
var available = window.crypto ? YES : window.mozCrypto || window.msCrypto || window.oCrypto ? YES | PREFIX : NO;
2252
passed = !!crypto && 'subtle' in crypto ? available : !!crypto && 'webkitSubtle' in crypto ? YES | PREFIX : NO;
2257
key: 'security.crypto',
2265
function (results) {
2268
if (navigator.webdriver && Browsers.isBrowser('Firefox', '>', 22)) {
2269
passed = YES | DISABLED;
2272
var item = results.addItem({
2273
key: 'security.csp10',
2277
window.addEventListener('message', function(e) {
2278
if (e.data === 'csp10:passed') {
2283
item.stopBackground();
2286
if (e.data === 'csp10:failed') {
2287
item.stopBackground();
2291
item.startBackground();
2293
var iframe = document.createElement('iframe');
2294
iframe.src = '/assets/csp.html';
2295
iframe.style.visibility = 'hidden';
2296
document.body.appendChild(iframe);
2298
window.setTimeout(function () {
2299
item.stopBackground();
2300
document.body.removeChild(iframe);
2307
function (results) {
2309
key: 'security.csp11',
2310
passed: 'SecurityPolicyViolationEvent' in window
2317
function (results) {
2319
key: 'security.cors',
2320
passed: window.XMLHttpRequest && 'withCredentials' in new XMLHttpRequest()
2325
/* subresource integrity */
2327
function (results) {
2329
key: 'security.integrity',
2330
passed: 'integrity' in document.createElement('link')
2337
function (results) {
2339
key: 'security.postMessage',
2340
passed: !!window.postMessage
2345
/* web authentication */
2347
function (results) {
2349
key: 'security.authentication',
2350
passed: 'webauthn' in window ? YES : 'msCredentials' in window ? YES | OLD : NO
2355
/* credential management */
2357
function (results) {
2359
key: 'security.credential',
2360
passed: 'credentials' in navigator
2365
/* sandboxed iframe */
2367
function (results) {
2369
key: 'security.sandbox',
2370
passed: 'sandbox' in document.createElement('iframe')
2377
function (results) {
2379
key: 'security.srcdoc',
2380
passed: 'srcdoc' in document.createElement('iframe')
2387
function (results) {
2389
key: 'payments.payments',
2390
passed: 'PaymentRequest' in window
2397
function (results) {
2399
key: 'other.history',
2400
passed: !!(window.history && history.pushState)
2407
function (results) {
2408
var element = document.createElement('video');
2411
key: 'video.element',
2412
passed: !!element.canPlayType
2416
/* audioTracks property */
2419
key: 'video.audiotracks',
2420
passed: 'audioTracks' in element
2424
/* videoTracks property */
2427
key: 'video.videotracks',
2428
passed: 'videoTracks' in element
2435
key: 'video.subtitle',
2436
passed: 'track' in document.createElement('track')
2443
key: 'video.poster',
2444
passed: 'poster' in element
2451
function (results) {
2452
var element = document.createElement('video');
2457
key: 'video.codecs.mp4.mpeg4',
2458
passed: !!element.canPlayType && canPlayType(element, 'video/mp4; codecs="mp4v.20.8"')
2463
/* I added a workaround for IE9, which only detects H.264 if you also provide an audio codec. Bug filed @ connect.microsoft.com */
2466
key: 'video.codecs.mp4.h264',
2467
passed: !!element.canPlayType && (canPlayType(element, 'video/mp4; codecs="avc1.42E01E"') || canPlayType(element, 'video/mp4; codecs="avc1.42E01E, mp4a.40.2"'))
2473
key: 'video.codecs.mp4.h265',
2474
passed: !!element.canPlayType && (canPlayType(element, 'video/mp4; codecs="hvc1.1.L0.0"') || canPlayType(element, 'video/mp4; codecs="hev1.1.L0.0"'))
2480
key: 'video.codecs.ogg.theora',
2481
passed: !!element.canPlayType && canPlayType(element, 'video/ogg; codecs="theora"')
2484
/* vp8 in webm codec */
2487
key: 'video.codecs.webm.vp8',
2488
passed: !!element.canPlayType && canPlayType(element, 'video/webm; codecs="vp8"')
2491
/* vp9 in webm codec */
2494
key: 'video.codecs.webm.vp9',
2495
passed: !!element.canPlayType && canPlayType(element, 'video/webm; codecs="vp9"')
2498
/* does codec detection work properly? */
2502
if (!!element.canPlayType) {
2503
if (element.canPlayType('video/nonsense') == 'no') {
2505
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')
2508
if (element.canPlayType('video/webm') == 'probably') {
2510
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')
2513
if (element.canPlayType('video/mp4; codecs="avc1.42E01E"') == 'maybe' && element.canPlayType('video/mp4') == 'probably') {
2515
log('BUGGY: Codec detection bug in iOS 4.1 and earlier that switches "maybe" and "probably" around')
2518
if (element.canPlayType('video/mp4; codecs="avc1.42E01E"') == 'maybe' && element.canPlayType('video/mp4') == 'maybe') {
2520
log('BUGGY: Codec detection bug in Android where no better answer than "maybe" is given')
2523
if (element.canPlayType('video/mp4; codecs="avc1.42E01E, mp4a.40.2"') == 'probably' && element.canPlayType('video/mp4; codecs="avc1.42E01E"') != 'probably') {
2525
log('BUGGY: Codec detection bug in Internet Explorer 9 that requires both audio and video codec on test')
2530
key: 'video.canplaytype',
2531
passed: element.canPlayType ? (passed ? YES : YES | BUGGY) : NO
2538
function (results) {
2539
var element = document.createElement('audio');
2542
key: 'audio.element',
2543
passed: !!element.canPlayType
2550
passed: 'loop' in element
2553
/* preload property */
2556
key: 'audio.preload',
2557
passed: 'preload' in element
2564
function (results) {
2565
var element = document.createElement('audio');
2570
key: 'audio.codecs.pcm',
2571
passed: !!element.canPlayType && canPlayType(element, 'audio/wav; codecs="1"')
2577
if (element.canPlayType) {
2578
var t = element.canPlayType('audio/mpeg');
2580
// We need to check if the browser really supports playing MP3s by loading one and seeing if the
2581
// loadedmetadata event is triggered... but for now assume it does support it...
2583
} else if (t == 'probably') {
2589
key: 'audio.codecs.mp3',
2596
key: 'audio.codecs.mp4.aac',
2597
passed: !!element.canPlayType && canPlayType(element, 'audio/mp4; codecs="mp4a.40.2"')
2603
key: 'audio.codecs.mp4.ac3',
2604
passed: !!element.canPlayType && canPlayType(element, 'audio/mp4; codecs="ac-3"')
2607
/* enhanced ac3 codec */
2610
key: 'audio.codecs.mp4.ec3',
2611
passed: !!element.canPlayType && canPlayType(element, 'audio/mp4; codecs="ec-3"')
2614
/* ogg vorbis codec */
2617
key: 'audio.codecs.ogg.vorbis',
2618
passed: !!element.canPlayType && canPlayType(element, 'audio/ogg; codecs="vorbis"')
2621
/* ogg opus codec */
2624
key: 'audio.codecs.ogg.opus',
2625
passed: !!element.canPlayType && canPlayType(element, 'audio/ogg; codecs="opus"')
2628
/* webm vorbis codec */
2631
key: 'audio.codecs.webm.vorbis',
2632
passed: !!element.canPlayType && canPlayType(element, 'audio/webm; codecs="vorbis"')
2635
/* webm opus codec */
2638
key: 'audio.codecs.webm.opus',
2639
passed: !!element.canPlayType && canPlayType(element, 'audio/webm; codecs="opus"')
2647
function (results) {
2649
key: 'audio.webaudio',
2650
passed: 'AudioContext' in window ? YES : 'webkitAudioContext' in window || 'mozAudioContext' in window || 'oAudioContext' in window || 'msAudioContext' in window ? YES | PREFIX : NO
2655
/* speech recognition */
2657
function (results) {
2659
key: 'audio.speechrecognition',
2660
passed: 'SpeechRecognition' in window ? YES : 'webkitSpeechRecognition' in window || 'mozSpeechRecognition' in window || 'oSpeechRecognition' in window || 'msSpeechRecognition' in window ? YES | PREFIX : NO
2665
/* speech synthesis */
2667
function (results) {
2668
var speechSynthesis = window.speechSynthesis || window.webkitSpeechSynthesis || window.mozSpeechSynthesis || window.oSpeechSynthesis || window.msSpeechSynthesis;
2669
var available = 'speechSynthesis' in window ? YES : 'webkitSpeechSynthesis' in window || 'mozSpeechSynthesis' in window || 'oSpeechSynthesis' in window || 'msSpeechSynthesis' in window ? YES | PREFIX : NO;
2670
var voices = speechSynthesis ? speechSynthesis.getVoices().length : 0;
2672
var speechItem = results.addItem({
2673
key: 'audio.speechsynthesis',
2674
passed: speechSynthesis && voices ? available : NO
2677
if (speechSynthesis && !voices) {
2678
if (speechSynthesis.addEventListener) {
2679
speechItem.startBackground();
2681
speechSynthesis.addEventListener("voiceschanged", function () {
2682
voices = speechSynthesis.getVoices().length;
2685
passed: voices ? available : NO
2688
speechItem.stopBackground();
2691
window.setTimeout(function () {
2692
speechItem.stopBackground();
2701
function (results) {
2702
var element = document.createElement('video');
2707
key: 'streaming.mediasource',
2708
passed: 'MediaSource' in window ? YES : 'WebKitMediaSource' in window || 'mozMediaSource' in window || 'msMediaSource' in window ? YES | PREFIX : NO
2714
key: 'streaming.drm',
2715
passed: 'setMediaKeys' in element ? YES : 'webkitAddKey' in element || 'webkitSetMediaKeys' in element || 'mozSetMediaKeys' in element || 'msSetMediaKeys' in element ? YES | PREFIX : NO
2718
/* dash streaming */
2721
key: 'streaming.type.dash',
2722
passed: !!element.canPlayType && element.canPlayType('application/dash+xml') != ''
2728
key: 'streaming.type.hls',
2729
passed: !!element.canPlayType && (element.canPlayType('application/vnd.apple.mpegURL') != '' || element.canPlayType('audio/mpegurl') != '')
2734
/* streaming video codecs */
2736
function (results) {
2738
/* mpeg-4 in mp4 codec */
2741
key: 'streaming.video.codecs.mp4.mpeg4',
2742
passed: 'MediaSource' in window && MediaSource.isTypeSupported('video/mp4; codecs="mp4v.20.8"')
2745
/* h.264 in mp4 codec */
2748
key: 'streaming.video.codecs.mp4.h264',
2749
passed: 'MediaSource' in window && MediaSource.isTypeSupported('video/mp4; codecs="avc1.42E01E"')
2752
/* h.265 in mp4 codec */
2755
key: 'streaming.video.codecs.mp4.h265',
2756
passed: 'MediaSource' in window && (MediaSource.isTypeSupported('video/mp4; codecs="hvc1.1.L0.0"') || MediaSource.isTypeSupported('video/mp4; codecs="hev1.1.L0.0"'))
2759
/* h.264 in ts codec */
2762
key: 'streaming.video.codecs.ts.h264',
2763
passed: 'MediaSource' in window && MediaSource.isTypeSupported('video/mp2t; codecs="avc1.42E01E"')
2766
/* h.265 in ts codec */
2769
key: 'streaming.video.codecs.ts.h265',
2770
passed: 'MediaSource' in window && (MediaSource.isTypeSupported('video/mp2t; codecs="hvc1.1.L0.0"') || MediaSource.isTypeSupported('video/mp2t; codecs="hev1.1.L0.0"'))
2776
key: 'streaming.video.codecs.ogg.theora',
2777
passed: 'MediaSource' in window && MediaSource.isTypeSupported('video/ogg; codecs="theora"')
2780
/* vp8 in webm codec */
2783
key: 'streaming.video.codecs.webm.vp8',
2784
passed: 'MediaSource' in window && MediaSource.isTypeSupported('video/webm; codecs="vp8"')
2787
/* vp9 in webm codec */
2790
key: 'streaming.video.codecs.webm.vp9',
2791
passed: 'MediaSource' in window && MediaSource.isTypeSupported('video/webm; codecs="vp9"')
2796
/* streaming audio codecs */
2798
function (results) {
2800
/* aac codec in mp4 */
2803
key: 'streaming.audio.codecs.mp4.aac',
2804
passed: 'MediaSource' in window && MediaSource.isTypeSupported('audio/mp4; codecs="mp4a.40.2"')
2807
/* ac3 codec in mp4 */
2810
key: 'streaming.audio.codecs.mp4.ac3',
2811
passed: 'MediaSource' in window && MediaSource.isTypeSupported('audio/mp4; codecs="ac-3"')
2814
/* enhanced ac3 codec in mp4 */
2817
key: 'streaming.audio.codecs.mp4.ec3',
2818
passed: 'MediaSource' in window && MediaSource.isTypeSupported('audio/mp4; codecs="ec-3"')
2821
/* aac codec in mp4 */
2824
key: 'streaming.audio.codecs.ts.aac',
2825
passed: 'MediaSource' in window && MediaSource.isTypeSupported('audio/mp2t; codecs="mp4a.40.2"')
2828
/* ac3 codec in mp4 */
2831
key: 'streaming.audio.codecs.ts.ac3',
2832
passed: 'MediaSource' in window && MediaSource.isTypeSupported('audio/mp2t; codecs="ac-3"')
2835
/* enhanced ac3 codec in mp4 */
2838
key: 'streaming.audio.codecs.ts.ec3',
2839
passed: 'MediaSource' in window && MediaSource.isTypeSupported('audio/mp2t; codecs="ec-3"')
2842
/* vorbis in ogg codec */
2845
key: 'streaming.audio.codecs.ogg.vorbis',
2846
passed: 'MediaSource' in window && MediaSource.isTypeSupported('audio/ogg; codecs="vorbis"')
2849
/* opus in ogg codec */
2852
key: 'streaming.audio.codecs.ogg.opus',
2853
passed: 'MediaSource' in window && MediaSource.isTypeSupported('audio/ogg; codecs="opus"')
2856
/* vorbis in webm codec */
2859
key: 'streaming.audio.codecs.webm.vorbis',
2860
passed: 'MediaSource' in window && MediaSource.isTypeSupported('audio/webm; codecs="vorbis"')
2863
/* opus in webm codec */
2866
key: 'streaming.audio.codecs.webm.opus',
2867
passed: 'MediaSource' in window && MediaSource.isTypeSupported('audio/webm; codecs="opus"')
2872
/* picture element */
2874
function (results) {
2876
key: 'responsive.picture',
2877
passed: 'HTMLPictureElement' in window
2882
/* srcset attribute */
2884
function (results) {
2886
key: 'responsive.srcset',
2887
passed: 'srcset' in document.createElement('img')
2892
/* sizes attribute */
2894
function (results) {
2896
key: 'responsive.sizes',
2897
passed: 'sizes' in document.createElement('img')
2902
/* canvas element and 2d context */
2904
function (results) {
2905
var canvas = document.createElement('canvas');
2908
key: 'canvas.context',
2909
passed: !!(canvas.getContext && typeof CanvasRenderingContext2D != 'undefined' && canvas.getContext('2d') instanceof CanvasRenderingContext2D)
2914
/* canvas drawing functions */
2916
function (results) {
2917
var canvas = document.createElement('canvas');
2922
if (canvas.getContext) {
2924
passed = typeof canvas.getContext('2d').fillText == 'function';
2935
/* ellipse support */
2938
if (canvas.getContext) {
2940
passed = typeof canvas.getContext('2d').ellipse != 'undefined';
2947
key: 'canvas.ellipse',
2951
/* dashed support */
2954
if (canvas.getContext) {
2956
passed = typeof canvas.getContext('2d').setLineDash != 'undefined';
2963
key: 'canvas.dashed',
2967
/* focusring support */
2970
if (canvas.getContext) {
2972
passed = typeof canvas.getContext('2d').drawFocusIfNeeded != 'undefined';
2979
key: 'canvas.focusring',
2983
/* hittest support */
2986
if (canvas.getContext) {
2988
passed = typeof canvas.getContext('2d').addHitRegion != 'undefined';
2995
key: 'canvas.hittest',
3003
function (results) {
3006
passed: typeof Path2D != "undefined" ? YES : typeof Path != "undefined" ? YES | OLD : NO
3011
/* blending support */
3013
function (results) {
3014
var canvas = document.createElement('canvas');
3018
if (canvas.getContext) {
3023
var ctx = canvas.getContext('2d');
3024
ctx.fillStyle = '#fff';
3025
ctx.fillRect(0, 0, 1, 1);
3026
ctx.globalCompositeOperation = 'screen';
3027
ctx.fillStyle = '#000';
3028
ctx.fillRect(0, 0, 1, 1);
3030
var data = ctx.getImageData(0, 0, 1, 1);
3032
passed = ctx.globalCompositeOperation == 'screen' && data.data[0] == 255;
3039
key: 'canvas.blending',
3045
/* export to image format */
3047
function (results) {
3048
var canvas = document.createElement('canvas');
3053
if (canvas.getContext) {
3055
passed = canvas.toDataURL('image/png').substring(5, 14) == 'image/png';
3066
/* export to jpeg */
3069
if (canvas.getContext) {
3071
passed = canvas.toDataURL('image/jpeg').substring(5, 15) == 'image/jpeg';
3082
/* export to jpeg xr */
3085
if (canvas.getContext) {
3087
passed = canvas.toDataURL('image/vnd.ms-photo').substring(5, 23) == 'image/vnd.ms-photo';
3094
key: 'canvas.jpegxr',
3098
/* export to webp */
3101
if (canvas.getContext) {
3103
passed = canvas.toDataURL('image/webp').substring(5, 15) == 'image/webp';
3118
function (results) {
3119
var element = document.createElement('canvas');
3121
var passed = 'WebGLRenderingContext' in window;
3123
var contexts = ['webgl', 'experimental-webgl', 'ms-webgl', 'moz-webgl', 'opera-3d', 'webkit-3d', 'ms-3d', '3d'];
3125
var enabled = false;
3127
for (var b = -1, len = contexts.length; ++b < len;) {
3129
if (element.getContext(contexts[b])) {
3130
context = contexts[b];
3139
passed: enabled ? (context == 'webgl' ? YES : (context == 'experimental-webgl' ? YES | EXPERIMENTAL : YES | PREFIX)) : (passed ? YES | DISABLED : NO)
3146
function (results) {
3147
var element = document.createElement('canvas');
3148
var contexts = ['webgl2', 'experimental-webgl2'];
3150
var enabled = false;
3152
var passed = 'WebGL2RenderingContext' in window;
3154
for (var b = -1, len = contexts.length; ++b < len;) {
3156
if (element.getContext(contexts[b])) {
3157
context = contexts[b];
3166
passed: enabled ? (context == 'webgl2' ? YES : (context == 'experimental-webgl2' ? YES | EXPERIMENTAL : YES | PREFIX)) : (passed ? YES | DISABLED : NO)
3173
function (results) {
3176
passed: 'getVRDisplays' in navigator ? YES : 'mozGetVRDevices' in navigator ? YES | PREFIX : NO
3183
function (results) {
3185
key: 'animation.webanimation',
3186
passed: 'animate' in document.createElement('div')
3191
/* requestAnimationFrame */
3193
function (results) {
3195
key: 'animation.requestAnimationFrame',
3196
passed: !!window.requestAnimationFrame ? YES : !!window.webkitRequestAnimationFrame || !!window.mozRequestAnimationFrame || !!window.msRequestAnimationFrame || !!window.oRequestAnimationFrame ? YES | PREFIX : NO
3201
/* applicationCache */
3203
function (results) {
3205
key: 'offline.applicationCache',
3206
passed: !!window.applicationCache
3213
function (results) {
3215
key: 'offline.serviceWorkers',
3216
passed: !!window.navigator.serviceWorker
3223
function (results) {
3225
key: 'offline.pushMessages',
3226
passed: 'PushManager' in window && 'PushSubscription' in window
3231
/* registerProtocolHandler */
3233
function (results) {
3235
key: 'offline.registerProtocolHandler',
3236
passed: !!window.navigator.registerProtocolHandler
3241
/* registerContentHandler */
3243
function (results) {
3245
key: 'offline.registerContentHandler',
3246
passed: !!window.navigator.registerContentHandler
3251
/* session storage */
3253
function (results) {
3255
key: 'storage.sessionStorage',
3256
passed: 'sessionStorage' in window && window.sessionStorage != null
3264
function (results) {
3267
passed = 'localStorage' in window && window.localStorage != null
3269
/* If we get a security exception we know the feature exists, but cookies are disabled */
3270
if (e.name == 'NS_ERROR_DOM_SECURITY_ERR' || e.name == 'SecurityError') {
3271
passed = YES | DISABLED;
3276
key: 'storage.localStorage',
3284
function (results) {
3289
indexedDB = window.indexedDB || window.webkitIndexedDB || window.mozIndexedDB || window.moz_indexedDB || window.oIndexedDB || window.msIndexedDB;
3290
passed = !!window.indexedDB ? YES : !!window.webkitIndexedDB || !!window.mozIndexedDB || !!window.moz_indexedDB || !!window.oIndexedDB || !!window.msIndexedDB ? YES | PREFIX : NO;
3292
if (indexedDB && ! 'deleteDatabase' in indexedDB) {
3294
log('BUGGY: missing deleteDatabase function on indexedDB');
3297
/* If we get a security exception we know the feature exists, but cookies are disabled */
3298
if (e.name == 'NS_ERROR_DOM_SECURITY_ERR' || e.name == 'SecurityError') {
3299
passed = YES | DISABLED;
3304
key: 'storage.indexedDB.basic',
3308
/* indexeddb blob and arraybuffer storage */
3310
var blobitem = results.addItem({
3311
key: 'storage.indexedDB.blob',
3312
passed: passed & DISABLED || passed & BUGGY ? NO | UNKNOWN : NO
3315
var arrayitem = results.addItem({
3316
key: 'storage.indexedDB.arraybuffer',
3317
passed: passed & DISABLED || passed & BUGGY ? NO | UNKNOWN : NO
3320
if (indexedDB && 'deleteDatabase' in indexedDB) {
3322
blobitem.startBackground();
3323
arrayitem.startBackground();
3325
var request = indexedDB.deleteDatabase('html5test');
3327
request.onerror = function (e) {
3328
blobitem.stopBackground();
3329
arrayitem.stopBackground();
3332
request.onsuccess = function () {
3333
var request = indexedDB.open('html5test', 1);
3335
request.onupgradeneeded = function () {
3336
request.result.createObjectStore("store");
3339
request.onerror = function (event) {
3340
blobitem.stopBackground();
3341
arrayitem.stopBackground();
3344
request.onsuccess = function () {
3345
var db = request.result;
3348
db.transaction("store", "readwrite").objectStore("store").put(new Blob(), "key");
3357
db.transaction("store", "readwrite").objectStore("store").put(new ArrayBuffer(), "key");
3366
blobitem.stopBackground();
3367
arrayitem.stopBackground();
3370
indexedDB.deleteDatabase('html5test');
3374
blobitem.stopBackground();
3375
arrayitem.stopBackground();
3383
function (results) {
3385
key: 'storage.sqlDatabase',
3386
passed: !!window.openDatabase
3393
function (results) {
3395
key: 'files.fileReader',
3396
passed: 'FileReader' in window
3399
/* file reader as blob */
3402
key: 'files.fileReader.blob',
3403
passed: 'Blob' in window
3406
/* file reader as data url */
3409
key: 'files.fileReader.dataURL',
3410
passed: 'FileReader' in window && 'readAsDataURL' in (new FileReader())
3413
/* file reader as array buffer */
3416
key: 'files.fileReader.arraybuffer',
3417
passed: 'FileReader' in window && 'readAsArrayBuffer' in (new FileReader())
3420
/* file reader as object url */
3423
key: 'files.fileReader.objectURL',
3424
passed: 'URL' in window && 'createObjectURL' in URL
3429
/* request file system */
3431
function (results) {
3433
key: 'files.fileSystem',
3434
passed: !!window.requestFileSystem ? YES : !!window.webkitRequestFileSystem || !!window.mozRequestFileSystem || !!window.oRequestFileSystem || !!window.msRequestFileSystem ? YES | PREFIX : NO
3439
/* get file system */
3441
function (results) {
3443
key: 'files.getFileSystem',
3444
passed: !!navigator.getFileSystem ? YES : !!navigator.webkitGetFileSystem || !!navigator.mozGetFileSystem || !!window.msGetFileSystem ? YES | PREFIX : NO
3449
/* readable streams */
3451
function (results) {
3453
key: 'streams.readable',
3454
passed: 'ReadableStream' in window
3460
/* writeable streams */
3462
function (results) {
3464
key: 'streams.writeable',
3465
passed: 'WriteableStream' in window
3470
/* custom elements */
3472
function (results) {
3474
key: 'components.custom',
3475
passed: 'registerElement' in document
3482
function (results) {
3484
key: 'components.shadowdom',
3485
passed: 'attachShadow' in document.createElement('div') ? YES : 'createShadowRoot' in document.createElement('div') || 'webkitCreateShadowRoot' in document.createElement('div') ? YES | OLD : NO
3492
function (results) {
3496
passed = 'content' in document.createElement('template');
3501
key: 'components.template',
3509
function (results) {
3511
key: 'components.imports',
3512
passed: 'import' in document.createElement('link')
3519
function (results) {
3521
key: 'scripting.async',
3522
passed: 'async' in document.createElement('script')
3527
/* deferred scripts */
3529
function (results) {
3531
key: 'scripting.defer',
3532
passed: 'defer' in document.createElement('script')
3537
/* script error reporting */
3539
function (results) {
3541
key: 'scripting.onerror',
3542
passed: isEventSupported('error')
3547
/* script execution events */
3549
function (results) {
3550
var executionevents = results.addItem({
3551
key: 'scripting.executionevents',
3555
executionevents.startBackground();
3559
var s = document.createElement('script');
3560
s.src = "data:text/javascript;charset=utf-8,window"
3562
s.addEventListener('beforescriptexecute', function () {
3566
s.addEventListener('afterscriptexecute', function () {
3568
executionevents.update({
3573
executionevents.stopBackground();
3576
document.body.appendChild(s);
3578
window.setTimeout(function () {
3579
executionevents.stopBackground();
3584
/* base64 encoding and decoding */
3586
function (results) {
3588
key: 'scripting.base64',
3589
passed: 'btoa' in window && 'atob' in window
3594
/* mutation observer */
3596
function (results) {
3598
key: 'scripting.mutationObserver',
3599
passed: 'MutationObserver' in window ? YES : 'WebKitMutationObserver' in window || 'MozMutationObserver' in window || 'oMutationObserver' in window || 'msMutationObserver' in window ? YES | PREFIX : NO
3606
function (results) {
3608
key: 'scripting.url',
3609
passed: 'URL' in window ? YES : 'WebKitURL' in window || 'MozURL' in window || 'oURL' in window || 'msURL' in window ? YES | PREFIX : NO
3614
/* text encoding api */
3616
function (results) {
3618
key: 'scripting.encoding',
3619
passed: 'TextEncoder' in window && 'TextDecoder' in window ? YES : NO
3624
/* json encoding and decoding */
3626
function (results) {
3628
key: 'scripting.es5.json',
3629
passed: 'JSON' in window && 'parse' in JSON
3634
/* array functions */
3636
function (results) {
3637
var passed = !!(Array.prototype &&
3638
Array.prototype.every &&
3639
Array.prototype.filter &&
3640
Array.prototype.forEach &&
3641
Array.prototype.indexOf &&
3642
Array.prototype.lastIndexOf &&
3643
Array.prototype.map &&
3644
Array.prototype.some &&
3645
Array.prototype.reduce &&
3646
Array.prototype.reduceRight &&
3650
key: 'scripting.es5.array',
3651
passed: passed ? YES : NO
3656
/* date functions */
3658
function (results) {
3659
var canParseISODate = false;
3662
canParseISODate = !!Date.parse('2013-04-12T06:06:37.307Z');
3666
var passed = !!(Date.now &&
3668
Date.prototype.toISOString &&
3669
Date.prototype.toJSON &&
3673
key: 'scripting.es5.date',
3674
passed: passed ? YES : NO
3679
/* function functions */
3681
function (results) {
3682
var passed = !!(Function.prototype && Function.prototype.bind);
3685
key: 'scripting.es5.function',
3686
passed: passed ? YES : NO
3691
/* object functions */
3693
function (results) {
3694
var passed = !!(Object.keys &&
3696
Object.getPrototypeOf &&
3697
Object.getOwnPropertyNames &&
3700
Object.isExtensible &&
3701
Object.getOwnPropertyDescriptor &&
3702
Object.defineProperty &&
3703
Object.defineProperties &&
3706
Object.preventExtensions);
3709
key: 'scripting.es5.object',
3710
passed: passed ? YES : NO
3717
function (results) {
3718
var passed = (function() {'use strict'; return !this; })()
3721
key: 'scripting.es5.strict',
3722
passed: passed ? YES : NO
3727
/* string functions */
3729
function (results) {
3730
var passed = !!(String.prototype && String.prototype.trim)
3733
key: 'scripting.es5.string',
3734
passed: passed ? YES : NO
3739
/* internationalisation api */
3741
function (results) {
3743
key: 'scripting.es6.i18n',
3744
passed: 'Intl' in window ? YES : NO
3751
function (results) {
3752
var passed = 'Promise' in window ? YES | OLD : NO;
3754
if ('Promise' in window &&
3755
'resolve' in window.Promise &&
3756
'reject' in window.Promise &&
3757
'all' in window.Promise &&
3758
'race' in window.Promise &&
3761
new window.Promise(function (r) { resolve = r; });
3762
return typeof resolve === 'function';
3768
key: 'scripting.es6.promises',
3776
function (results) {
3780
eval('const a = 1');
3786
key: 'scripting.es6.const',
3794
function (results) {
3804
key: 'scripting.es6.let',
3810
/* arrow functions */
3812
function (results) {
3822
key: 'scripting.es6.arrow',
3830
function (results) {
3834
eval('class Something {}');
3840
key: 'scripting.es6.class',
3848
function (results) {
3852
eval('function* test() {}');
3858
key: 'scripting.es6.generators',
3864
/* template strings */
3866
function (results) {
3870
eval('var a = `a`');
3876
key: 'scripting.es6.template',
3884
function (results) {
3888
eval('var { first: f, last: l } = { first: "Jane", last: "Doe" }');
3894
key: 'scripting.es6.destructuring',
3902
function (results) {
3906
eval('Math.max(...[ 5, 10 ])');
3912
key: 'scripting.es6.spread',
3918
/* default params */
3920
function (results) {
3924
eval('function test (one = 1) {}');
3930
key: 'scripting.es6.defaultparams',
3938
function (results) {
3940
key: 'scripting.es6.symbol',
3941
passed: typeof Symbol !== 'undefined' ? YES : NO
3948
function (results) {
3949
var passed = typeof Map !== 'undefined' &&
3950
typeof WeakMap !== 'undefined' &&
3951
typeof Set !== 'undefined' &&
3952
typeof WeakSet !== 'undefined';
3955
key: 'scripting.es6.collections',
3956
passed: passed ? YES : NO
3961
/* array functions */
3963
function (results) {
3964
var passed = typeof Array.prototype.find !== 'undefined' &&
3965
typeof Array.prototype.findIndex !== 'undefined' &&
3966
typeof Array.from !== 'undefined' &&
3967
typeof Array.of !== 'undefined' &&
3968
typeof Array.prototype.entries !== 'undefined' &&
3969
typeof Array.prototype.keys !== 'undefined' &&
3970
typeof Array.prototype.copyWithin !== 'undefined' &&
3971
typeof Array.prototype.fill !== 'undefined';
3974
key: 'scripting.es6.array',
3975
passed: passed ? YES : NO
3980
/* string functions */
3982
function (results) {
3983
var passed = typeof String.fromCodePoint !== 'undefined' &&
3984
typeof String.raw !== 'undefined' &&
3985
typeof String.prototype.codePointAt !== 'undefined' &&
3986
typeof String.prototype.repeat !== 'undefined' &&
3987
typeof String.prototype.startsWith !== 'undefined' &&
3988
typeof String.prototype.endsWith !== 'undefined' &&
3989
typeof String.prototype.includes !== 'undefined';
3992
key: 'scripting.es6.string',
3993
passed: passed ? YES : NO
3998
/* number functions */
4000
function (results) {
4001
var passed = !!(Number.isFinite &&
4003
Number.isSafeInteger &&
4006
Number.parseFloat &&
4007
Number.isInteger(Number.MAX_SAFE_INTEGER) &&
4008
Number.isInteger(Number.MIN_SAFE_INTEGER) &&
4009
Number.isFinite(Number.EPSILON));
4012
key: 'scripting.es6.number',
4013
passed: passed ? YES : NO
4018
/* object functions */
4020
function (results) {
4021
var passed = !!(Object.assign &&
4023
Object.setPrototypeOf);
4026
key: 'scripting.es6.object',
4027
passed: passed ? YES : NO
4032
/* math functions */
4034
function (results) {
4035
var passed = !!(Math &&
4055
key: 'scripting.es6.math',
4056
passed: passed ? YES : NO
4063
function (results) {
4065
key: 'scripting.es6.datatypes.ArrayBuffer',
4066
passed: typeof ArrayBuffer != 'undefined'
4070
key: 'scripting.es6.datatypes.Int8Array',
4071
passed: typeof Int8Array != 'undefined'
4075
key: 'scripting.es6.datatypes.Uint8Array',
4076
passed: typeof Uint8Array != 'undefined'
4080
key: 'scripting.es6.datatypes.Uint8ClampedArray',
4081
passed: typeof Uint8ClampedArray != 'undefined'
4085
key: 'scripting.es6.datatypes.Int16Array',
4086
passed: typeof Int16Array != 'undefined'
4090
key: 'scripting.es6.datatypes.Uint16Array',
4091
passed: typeof Uint16Array != 'undefined'
4095
key: 'scripting.es6.datatypes.Int32Array',
4096
passed: typeof Int32Array != 'undefined'
4100
key: 'scripting.es6.datatypes.Uint32Array',
4101
passed: typeof Uint32Array != 'undefined'
4105
key: 'scripting.es6.datatypes.Float32Array',
4106
passed: typeof Float32Array != 'undefined'
4110
key: 'scripting.es6.datatypes.Float64Array',
4111
passed: typeof Float64Array != 'undefined'
4115
key: 'scripting.es6.datatypes.DataView',
4116
passed: typeof DataView != 'undefined'
4120
var passed = typeof ArrayBuffer != 'undefined' &&
4121
typeof Int8Array != 'undefined' &&
4122
typeof Uint8Array != 'undefined' &&
4123
typeof Uint8ClampedArray != 'undefined' &&
4124
typeof Int16Array != 'undefined' &&
4125
typeof Uint16Array != 'undefined' &&
4126
typeof Int32Array != 'undefined' &&
4127
typeof Uint32Array != 'undefined' &&
4128
typeof Float32Array != 'undefined' &&
4129
typeof Float64Array != 'undefined' &&
4130
typeof DataView != 'undefined';
4133
key: 'scripting.es6.datatypes',
4134
passed: passed ? YES : NO
4141
function (results) {
4142
var item = results.addItem({
4143
key: 'scripting.es6.modules',
4147
item.startBackground();
4149
var callback = item.getGlobalCallback(function(scoped) {
4151
passed: scoped ? YES : YES | BUGGY
4155
log('BUGGY: Non-exported variables are not scoped to the ES6 module');
4158
item.stopBackground();
4161
var s = document.createElement('script');
4163
s.src = "data:text/javascript;charset=utf-8,var test_module_scope = true; window." + callback + "(typeof window.test_module_scope === 'undefined')";
4164
document.body.appendChild(s);
4166
window.setTimeout(function () {
4167
item.stopBackground();
4172
/* async await api */
4174
function (results) {
4178
eval('async function a() { return await Promise.resolve() }');
4184
key: 'scripting.es7.async',
4190
/* page visiblity */
4192
function (results) {
4194
key: 'other.pagevisiblity',
4195
passed: 'visibilityState' in document ? YES : 'webkitVisibilityState' in document || 'mozVisibilityState' in document || 'oVisibilityState' in document || 'msVisibilityState' in document ? YES | PREFIX : NO
4202
function (results) {
4204
key: 'other.getSelection',
4205
passed: !!window.getSelection
4210
/* scrollIntoView */
4212
function (results) {
4214
key: 'other.scrollIntoView',
4215
passed: 'scrollIntoView' in document.createElement('div')
4223
/* Helper functions */
4225
var isEventSupported = (function () {
4227
'select': 'input', 'change': 'input', 'input': 'input',
4228
'submit': 'form', 'reset': 'form', 'forminput': 'form', 'formchange': 'form',
4229
'error': 'img', 'load': 'img', 'abort': 'img'
4232
function isEventSupported(eventName, element) {
4233
element = element || document.createElement(TAGNAMES[eventName] || 'div');
4234
eventName = 'on' + eventName;
4236
var isSupported = (eventName in element);
4239
if (!element.setAttribute) {
4240
element = document.createElement('div');
4242
if (element.setAttribute && element.removeAttribute) {
4243
element.setAttribute(eventName, '');
4244
isSupported = typeof element[eventName] == 'function';
4246
if (typeof element[eventName] != 'undefined') {
4247
element[eventName] = void 0;
4249
element.removeAttribute(eventName);
4257
return isEventSupported;
4260
var log = function () {
4261
if (typeof console != 'undefined') {
4262
console.log.apply(console, arguments);
4266
var createInput = function (type) {
4267
var field = document.createElement('input');
4270
field.setAttribute('type', type);
4277
var canPlayType = function (element, type) {
4279
There is a bug in iOS 4.1 or earlier where probably and maybe are switched around.
4280
This bug was reported and fixed in iOS 4.2
4283
if (Browsers.isOs('iOS', '<', '4.2'))
4284
return element.canPlayType(type) == 'probably' || element.canPlayType(type) == 'maybe';
4286
return element.canPlayType(type) == 'probably';
4289
var closesImplicitly = function (name) {
4290
var foo = document.createElement('div');
4291
foo.innerHTML = '<p><' + name + '></' + name + '>';
4292
return foo.childNodes.length == 2;
4295
var getStyle = function (element, name) {
4296
function camelCase(str) {
4297
return str.replace(/-\D/g, function (match) {
4298
return match.charAt(1).toUpperCase()
4302
if (element.style[name]) {
4303
return element.style[name];
4304
} else if (element.currentStyle) {
4305
return element.currentStyle[camelCase(name)];
4307
else if (document.defaultView && document.defaultView.getComputedStyle) {
4308
s = document.defaultView.getComputedStyle(element, "");
4309
return s && s.getPropertyValue(name);
4315
var isBlock = function (element) {
4316
return getStyle(element, 'display') == 'block';
4319
var isHidden = function (element) {
4320
return getStyle(element, 'display') == 'none';
4328
function List(parent) { this.initialize(parent); }
4330
initialize: function (parent) {
4331
this.parent = parent;
4335
addItem: function (data) {
4336
var i = new Item(this, data);
4341
toString: function () {
4344
for (var i = 0; i < this.items.length; i++) {
4345
if (typeof this.items[i].data.passed != 'undefined') value.push(this.items[i].data.key + '=' + (+this.items[i].data.passed));
4348
return value.join(',');
4352
function Item(list, data) { this.initialize(list, data); }
4354
initialize: function (list, data) {
4358
if (typeof this.data.passed == 'undefined') this.data.passed = false;
4360
if (this.data.passed) {
4361
var blacklist = this.isOnBlacklist();
4363
this.data.passed = blacklist;
4368
update: function (data) {
4369
for (var key in data) {
4370
this.data[key] = data[key];
4373
if (typeof this.data.passed == 'undefined') this.data.passed = false;
4375
if (this.data.passed) {
4376
var blacklist = this.isOnBlacklist();
4378
this.data.passed = blacklist;
4383
isOnBlacklist: function () {
4385
var parts = this.data.key.replace(/\-/g, '.').split('.');
4387
for (var i = 0; i < parts.length; i++) {
4388
part += (i == 0 ? '' : '.') + parts[i];
4390
for (var k = 0; k < blacklists.length; k++) {
4391
if (typeof blacklists[k][1][part] != 'undefined') {
4392
if (blacklists[k][1][part]) {
4393
log('BLOCKED: ' + part + ' is on the blacklist for this browser!');
4394
return blacklists[k][0];
4403
startBackground: function () {
4404
this.list.parent.startBackground(this.data.key);
4407
stopBackground: function () {
4408
this.list.parent.stopBackground(this.data.key);
4411
getGlobalCallback: function(callback) {
4412
var uniqueid = (((1 + Math.random()) * 0x1000000) | 0).toString(16).substring(1);
4415
window['callback_' + uniqueid] = function() {
4416
callback.apply(that, arguments);
4419
return 'callback_' + uniqueid;
4423
function Runner(callback, error) { this.initialize(callback, error); }
4424
Runner.prototype = {
4425
initialize: function (callback, error) {
4430
'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'),
4431
'form.date.ui': Browsers.isBrowser('Sogou Explorer') || Browsers.isBrowser('Maxthon', '<', '4.0.5') || (Browsers.isBrowser('UC Browser', '<', '8.6') && Browsers.isType('mobile', 'tablet')),
4432
'form.month.ui': Browsers.isBrowser('Sogou Explorer') || Browsers.isBrowser('Maxthon', '<', '4.0.5') || (Browsers.isBrowser('UC Browser', '<', '8.6') && Browsers.isType('mobile', 'tablet')),
4433
'form.week.ui': Browsers.isBrowser('Sogou Explorer') || Browsers.isBrowser('Maxthon', '<', '4.0.5') || (Browsers.isBrowser('UC Browser', '<', '8.6') && Browsers.isType('mobile', 'tablet')),
4434
'form.time.ui': Browsers.isBrowser('Sogou Explorer') || Browsers.isBrowser('Maxthon', '<', '4.0.5') || (Browsers.isBrowser('UC Browser', '<', '8.6') && Browsers.isType('mobile', 'tablet')),
4435
'form.datetime-local.ui': Browsers.isBrowser('Sogou Explorer') || Browsers.isBrowser('Maxthon', '<', '4.0.5') || (Browsers.isBrowser('UC Browser', '<', '8.6') && Browsers.isType('mobile', 'tablet')),
4436
'form.color.ui': Browsers.isBrowser('Sogou Explorer') || (Browsers.isBrowser('UC Browser', '<', '9.8') && Browsers.isType('mobile', 'tablet')),
4437
'form.range.ui': (Browsers.isBrowser('UC Browser', '<', '9.8') && Browsers.isType('mobile', 'tablet')),
4438
'form.progress.element': Browsers.isBrowser('Baidu Browser'),
4439
'files.fileSystem': Browsers.isOs('BlackBerry Tablet OS'),
4440
'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'),
4441
'input.getGamepads': Browsers.isDevice('webOS TV') || Browsers.isDevice('Playstation 4') || Browsers.isDevice('Wii U'),
4442
'location.geolocation': Browsers.isDevice('webOS TV') || Browsers.isDevice('Xbox One') || Browsers.isBrowser('Baidu Browser') || Browsers.isOs('Google TV'),
4443
'location.orientation': Browsers.isBrowser('Baidu Browser'),
4444
'output.notifications': Browsers.isBrowser('Opera', '=', '18') || Browsers.isBrowser('Baidu Browser') || Browsers.isBrowser('Sogou Explorer'),
4445
'output.requestFullScreen': Browsers.isBrowser('Sogou Explorer') || Browsers.isOs('BlackBerry Tablet OS') || Browsers.isOs('BlackBerry OS'),
4446
'video.subtitle': Browsers.isBrowser('Baidu Browser') || Browsers.isBrowser('Sogou Explorer'),
4447
'3d.webgl': Browsers.isBrowser('Baidu Browser')
4454
'elements.semantic.ping': Browsers.isBrowser('Firefox') || Browsers.isBrowser('Firefox Mobile')
4461
'interaction.dragdrop': !(Browsers.isType('desktop') ||
4462
Browsers.isType('mobile', 'tablet', 'media') && (
4463
Browsers.isBrowser('Opera') && Browsers.isEngine('Presto')
4465
Browsers.isType('television') && (
4466
Browsers.isDevice('webOS TV')
4470
'interaction.editing': !(Browsers.isType('desktop') ||
4471
Browsers.isType('mobile', 'tablet', 'media') && (
4472
Browsers.isOs('iOS', '>=', '5') ||
4473
Browsers.isOs('Android', '>=', '4') ||
4474
Browsers.isOs('Windows Phone', '>=', '7.5') ||
4475
Browsers.isOs('BlackBerry') ||
4476
Browsers.isOs('BlackBerry OS') ||
4477
Browsers.isOs('BlackBerry Tablet OS') ||
4478
Browsers.isOs('Meego') ||
4479
Browsers.isOs('Tizen') ||
4480
Browsers.isEngine('Gecko') ||
4481
Browsers.isEngine('Presto') ||
4482
Browsers.isBrowser('Chrome') ||
4483
Browsers.isBrowser('Polaris', '>=', '8')
4485
Browsers.isType('television') && (
4486
Browsers.isOs('Tizen') ||
4487
Browsers.isDevice('webOS TV') ||
4488
Browsers.isBrowser('Espial') ||
4489
Browsers.isBrowser('MachBlue XT') ||
4490
Browsers.isEngine('Presto', '>=', '2.9')
4492
Browsers.isType('gaming') && (
4493
Browsers.isDevice('Xbox 360') ||
4494
Browsers.isDevice('Xbox One') ||
4495
Browsers.isDevice('Playstation 4')
4503
this.backgroundTasks = [];
4504
this.backgroundIds = {};
4505
this.backgroundId = 0;
4507
this.callback = callback;
4509
this.list = new List(this);
4511
for (var s = 0; s < testsuite.length; s++) {
4512
testsuite[s](this.list);
4515
this.waitForBackground();
4522
waitForBackground: function () {
4525
window.setTimeout(function () {
4526
that.checkForBackground.call(that);
4530
checkForBackground: function () {
4532
for (var task = 0; task < this.backgroundTasks.length; task++) { running += this.backgroundTasks[task] }
4535
this.waitForBackground();
4541
startBackground: function (id) {
4542
var i = this.backgroundId++;
4543
this.backgroundIds[id] = i;
4544
this.backgroundTasks[i] = 1;
4547
stopBackground: function (id) {
4548
this.backgroundTasks[this.backgroundIds[id]] = 0;
4551
finished: function () {
4552
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);
4557
results: this.list.toString()