-
Notifications
You must be signed in to change notification settings - Fork 3k
/
caniuse.js
396 lines (357 loc) · 14.5 KB
/
caniuse.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
/*global UAParser*/
window.caniusecb = function(caniuse) {
var ua = new UAParser(navigator.userAgent).getResult();
if (ua.browser.name === 'Chrome Headless') {
// TODO:: We could test against the caniuse data of the standard Chrome browser but there are currently three
// errors already present (focuswithin siblinggeneral htmlimports) which need to be fixed first. For now
// just return like back with phantomjs
//ua.browser.name = 'Chrome';
return;
}
describe('caniuse', function() {
var unusedModernizr = [];
var unusedCaniuse = _.keys(caniuse.data);
// TODO:: This map could (theoretically!) be build automatically by going through all feature-detects and look into
// the docs where property and caniuse tags are written down. One should anyway look if some are missing here or in
// the feature detect docs
var map = {
adownload: 'download',
aping: 'ping',
areaping: 'ping',
ambientlight: 'ambient-light',
apng: 'apng',
appearance: 'css-appearance',
applicationcache: 'offline-apps',
atobbtoa: 'atob-btoa',
audio: 'audio',
avif: 'avif',
backdropfilter: 'css-backdrop-filter',
backgroundblendmode: 'css-backgroundblendmode',
bgpositionshorthand: 'css-background-offsets',
blobconstructor: 'blobbuilder',
bloburls: 'bloburls',
borderimage: 'border-image',
borderradius: 'border-radius',
boxdecorationbreak: 'css-boxdecorationbreak',
boxshadow: 'css-boxshadow',
boxsizing: 'css3-boxsizing',
broadcastchannel: 'broadcastchannel',
canvas: 'canvas',
canvasblending: 'canvas-blending',
canvastext: 'canvas-text',
checked: 'css-sel3',
classlist: 'classlist',
contenteditable: 'contenteditable',
contextmenu: 'menu',
cors: 'cors',
cryptography: 'cryptography',
cssanimations: 'css-animation',
csscalc: 'calc',
csschunit: 'ch-unit',
csscolumns: 'multicolumn',
cssfilters: 'css-filters',
cssgradients: 'css-gradients',
csshyphens: 'css-hyphens',
cssmask: 'css-masks',
csspointerevents: 'pointer-events',
csspositionsticky: 'css-sticky',
cssreflections: 'css-reflections',
cssremunit: 'rem',
cssresize: 'css-resize',
csstransforms: 'transforms2d',
csstransforms3d: 'transforms3d',
csstransitions: 'css-transitions',
cssvhunit: 'viewport-units',
cssvmaxunit: 'viewport-units',
cssvminunit: 'viewport-units',
cssvwunit: 'viewport-units',
cssaspectratio:'mdn-css_properties_aspect-ratio',
customelements: 'custom-elementsv1',
customproperties: 'css-variables',
dataset: 'dataset',
datauri: 'datauri',
details: 'details',
deviceorientation: 'deviceorientation',
displaytable: 'css-table',
ellipsis: 'text-overflow',
eventlistener: 'addeventlistener',
eventsource: 'eventsource',
fetch: 'fetch',
fileinput: 'forms',
filereader: 'fileapi',
filesystem: 'filesystem',
flexbox: 'flexbox',
flexboxlegacy: 'flexbox',
flexboxtweener: 'flexbox',
focusvisible: 'css-focus-visible',
focuswithin: 'css-focus-within',
fontdisplay: 'css-font-rendering-controls',
fontface: 'fontface',
formvalidationapi: 'form-validation',
fullscreen: 'fullscreen',
gamepads: 'gamepad',
geolocation: 'geolocation',
getrandomvalues: 'getrandomvalues',
getusermedia: 'stream',
hashchange: 'hashchange',
hidden: 'hidden',
history: 'history',
hsla: 'css3-colors',
htmlimports: 'imports',
indexeddb: 'indexeddb',
indexeddb2: 'indexeddb2',
inlinesvg: 'svg-html5',
inputtypes: 'forms',
intersectionobserver: 'intersectionobserver',
intl: 'internationalization',
jpeg2000: 'jpeg2000',
jpegxr: 'jpegxr',
json: 'json',
lastchild: 'css-sel3',
ligatures: 'font-feature',
localstorage: 'namevalue-storage',
mathml: 'mathml',
mediaqueries: 'css-mediaqueries',
medirecorder: 'mediarecorder',
mediasource: 'mediasource',
messagechannel: 'channel-messaging',
meter: 'progress',
multiplebgs: 'multibackgrounds',
mutationobserver: 'mutationobserver',
notification: 'notifications',
nthchild: 'css-sel3',
objectfit: 'object-fit',
opacity: 'css-opacity',
pagevisibility: 'pagevisibility',
passiveeventlisteners: 'passive-event-listener',
peerconnection: 'rtcpeerconnection',
performance: 'nav-timing',
picture: 'picture',
pointerevents: 'pointer',
postmessage: 'x-doc-messaging',
prefetch: 'link-rel-prefetch',
progressbar: 'progress',
promises: 'promises',
proximity: 'proximity',
pushmanager: 'mdn-api_pushmanager',
queryselector: 'queryselector',
regions: 'css-regions',
requestanimationframe: 'requestanimationframe',
resizeobserver: 'resizeobserver',
rgba: 'css3-colors',
ruby: 'ruby',
sandbox: 'iframe-sandbox',
scriptasync: 'script-async',
scriptdefer: 'script-defer',
scrollsnappoints: 'css-snappoints',
seamless: 'iframe-seamless',
serviceworker: 'serviceworkers',
shapes: 'css-shapes',
sharedworkers: 'sharedworkers',
siblinggeneral: 'css-sel3',
smil: 'svg-smil',
speechrecognition: 'speech-recognition',
speechsynthesis: 'speech-synthesis',
srcdoc: 'iframe-srcdoc',
srcset: 'srcset',
strictmode: 'use-strict',
stylescoped: 'style-scoped',
supports: 'css-featurequeries',
svg: 'svg',
svgasimg: 'svg-img',
svgfilters: 'svg-filters',
target: 'css-sel3',
template: 'template',
textalignlast: 'css-text-align-last',
textdecoration: 'text-decoration',
textshadow: 'css-textshadow',
touchevents: 'touch',
typedarrays: 'typedarrays',
unicoderange: 'font-unicode-range',
urlsearchparams: 'urlsearchparams',
userselect: 'user-select-none',
vibrate: 'vibration',
video: 'video',
webanimations: 'web-animation',
webaudio: 'audio-api',
webgl: 'webgl',
webp: 'webp',
websockets: 'websockets',
websqldatabase: 'sql-storage',
webworkers: 'webworkers',
workertypeoption:'mdn-api_worker_worker_ecmascript_modules',
willchange: 'will-change',
xhr2: 'xhr2'
};
// translate 'y' 'n' or 'a' into a boolean that Modernizr uses
function bool(result) {
// To handle correctly things like 'y #1' or 'a #2'
if (result.indexOf('y') === 0 || result.indexOf('a') === 0) {
return true;
}
// 'p' is for polyfill
if (result.indexOf('n') === 0 || result.indexOf('p') === 0) {
return false;
}
throw 'unknown return value from can i use - ' + result;
}
function testify(o) {
var ciubool = bool(o.caniuseResult);
/**************************************************************
* `o.caniuseResult` maps to the caniuse `stat` property
*
* y - (Y)es, supported by default
* a - (A)lmost supported (aka Partial support)
* n - (N)o support, or disabled by default
* p - No support, but has (P)olyfill
* u - Support (u)nknown
* x - Requires prefi(x) to work
* d - (D)isabled by default (need to enable flag or something)
**************************************************************/
// caniuse says audio/video are yes/no, Modernizr has more detail which we'll dumb down.
if (_.includes(['video', 'audio', 'webglextensions'], o.feature)) {
o.result = o.result.valueOf();
}
// webgl `partial` support means that not all users with these browsers have
// WebGL access, so we just ignore this test, and only check if the browser
// either fully supports or does not support
if (o.feature === 'webgl' && o.caniuseResult.indexOf('a') === 0) {
return;
}
// change the *documented* false positives
if (!ciubool && (o.feature === 'textshadow' && o.browser === 'Firefox' && o.version === 3)) {
ciubool = o.fp = true;
}
// firefox does not support unicode-range without a flag
if (o.feature === 'unicoderange' && o.caniuseResult.indexOf('y') === 0 && o.browser === 'Firefox' && o.version <= 40) {
return;
}
// firefox only supports web animation when a flag is enabled, which we
// don't do on sauce
if (o.feature === 'webanimations' && o.caniuseResult.indexOf('a d') === 0) {
return;
}
// firefox only supports the `url` version of css-filters, which we don't
// consider support
if (o.feature === 'cssfilters' && o.browser === 'Firefox' && o.caniuseResult.indexOf('a') === 0) {
return;
}
// before 4.0, firefox only supports MathML on XHTML documents. Since we
// don't run inside of one, we will have a technically false negative
if (o.feature === 'mathml' && o.browser === 'Firefox' && o.version < 4) {
return;
}
// caniuse bundles viewport units, all of which work in IE 9+, save for vmax
// we skip this comparison with a version gate, hoping its fixed in later versions.
if (o.feature === 'cssvmaxunit' && o.caniuseResult.indexOf('a') === 0) {
return;
}
// safari 5 does not support the `FileReader` API, which we test as a requirement
if (o.feature === 'filereader' && o.caniuseResult.indexOf('a') === 0) {
return;
}
// safari 5 and 6 only support the old version of WebSockets, which breaks most servers.
// As a result, we mark it as not supported, and ignore the caniuse match
if (o.feature === 'websockets' && o.caniuseResult.indexOf('a') === 0) {
return;
}
// safari 7 recognizes the `seamless` attribute but does not actually support it
if (o.feature === 'seamless' && o.browser === 'Safari' && o.version === 7) {
return;
}
// caniuse counts a partial support for CORS via the XDomainRequest,
// but thats not really cors - so skip the comparison.
if (o.feature === 'cors' && o.browser === 'IE' && o.version < 10) {
return;
}
// Opera 12 has a false positive for `defer`
if (o.feature === 'scriptdefer' && o.browser === 'Opera' && parseInt(o.version, 10) === 12) {
return;
}
// caniuse bundles forms into one big wad of detects. we check to see if their result matches
// atleast some of our inputtypes.
if (o.ciufeature === 'forms') {
return it('Caniuse result for forms matches Modernizr\'s result for inputtypes', function() {
return expect(ciubool).to.be.equal(_.some(Modernizr.inputtypes, function(modernizrResult) {
return modernizrResult;
}));
});
}
// we breakout flexbox sniffing into three separate detects, which borks the caniuse mappings,
// since no browser supports all three
if (o.ciufeature === 'flexbox') {
return it('Caniuse result for flexbox matches Modernizr\'s result for flexbox', function() {
return expect([
Modernizr.flexbox,
Modernizr.flexboxlegacy,
Modernizr.flexboxtweener
]).to.contain(ciubool);
});
}
// caniuse bundles progress and meter elements, so we do too.
if (_.includes(['meter', 'progressbar'], o.feature)) {
return it('Caniuse result for ' + o.ciufeature + ' matches Modernizr\'s result for ' + o.feature, function() {
return expect([
Modernizr.meter,
Modernizr.progressbar
]).to.contain(ciubool);
});
}
// caniuse counts `filter` opacity as partial support - we don't.
if (o.feature === 'opacity' && o.browser === 'IE' && o.version < 9) {
return;
}
// if caniuse gave us a 'partial', lets let it pass with a note.
if (o.caniuseResult.indexOf('a') === 0) {
return it(o.browser + o.version + ': Caniuse reported partial support for ' + o.ciufeature, function() {
var modernizrResult = o.result instanceof Boolean ? o.result.valueOf() : !!o.result;
expect(ciubool).to.be.equal(modernizrResult);
});
}
// where we actually do most our assertions
it(o.browser + o.version + ': Caniuse result for ' + o.ciufeature + ' matches Modernizr\'s ' + (o.fp ? '*false positive*' : 'result') + ' for ' + o.feature, function() {
var modernizrResult = o.result instanceof Boolean ? o.result.valueOf() : !!o.result;
expect(ciubool).to.be.equal(modernizrResult);
});
}
_.forEach(Modernizr, function(result, feature) {
var caniuseFeatureName = map[feature];
if (_.isUndefined(caniuseFeatureName)) {
return unusedModernizr.push(feature);
}
var caniuseFeatureData = caniuse.data[caniuseFeatureName];
if (caniuseFeatureData === undefined) {
throw 'unknown key of caniusedata - ' + caniuseFeatureName;
}
unusedCaniuse = _.without(unusedCaniuse, caniuseFeatureName);
// get results for this feature for all versions of this browser
var browserResults = caniuseFeatureData.stats[ua.browser.name.toLowerCase()];
var majorminor = ua.browser.version
// opera gets grouped in some cases by caniuse
.replace(/(9\.(6|5))/ , ua.browser.name === 'Opera' ? '9.5-9.6' : '$1')
.replace(/(10\.(0|1))/, ua.browser.name === 'Opera' ? '10.0-10.1' : '$1');
// make sure the version keys of the caniusedata is sorted as numbers not as strings
// otherwise for example firefox 3.6 is the first version in the _.findLast call up next
var sortedVersionKeys = _.keys(browserResults).sort(function(key1, key2) {
return parseFloat(key1) - parseFloat(key2);
});
var versionToUse = _.findLast(sortedVersionKeys, function(ciuVersion) {
return parseFloat(ciuVersion) <= parseFloat(majorminor);
});
var latestResult = browserResults[versionToUse];
if (latestResult && latestResult !== 'u') { // 'y' 'n' or 'a'
// data ends w/ ` x` if its still prefixed in the imp
latestResult = latestResult.replace(' x', '');
// match it against our data.
testify({
feature: feature,
ciufeature: caniuseFeatureName,
result: Modernizr[feature],
caniuseResult: latestResult,
browser: ua.browser.name,
version: parseFloat(versionToUse)
});
}
});
});
};