2 use List::Util qw(sum max first);
3 no if $] >= 5.018, warnings => 'experimental::smartmatch';
6 title => 'browser compatibility cheat sheet',
9 "Compatibility table of new web features (HTML5, CSS3, SVG, Javascript)",
10 "comparing support and usage share for all popular browser versions.",
13 web browser support compatibility usage matrix available feature
14 html html5 css css3 svg javascript js dom mobile
15 ie internet explorer firefox chrome safari webkit opera
17 stylesheet => [qw'circus dark mono red light'],
18 data => ['data/browser/support.inc.pl'],
21 say "<h1>Browser compatibility</h1>\n";
23 my $caniuse = do 'data/browser/support.inc.pl' or die $@ || $!;
25 # mark last three (future) versions as unreleased, ensure current isn't
27 $_->[-1] => 0, $_->[-2] => 0, $_->[-3] => 0,
30 } for values %{ $caniuse->{agents} };
51 p => 'plugin required',
52 j => 'javascript required',
55 d => 'disabled by default',
60 ($caniuse->{agents}->{$_[0]}->{prefix_exceptions} // {})->{$_[1]}
61 // $caniuse->{agents}->{$_[0]}->{prefix} // (),
65 my %PSTATS = ( # score percentage
67 a => .5, 'a x' => .5, 'a d' => .1,
68 j => .2, 'p j' => .2, 'n d' => .2, 'n x d' => .2,
69 p => .2, 'p p' => .2, 'p d' => .1,
73 unoff => 'l1', # unofficial
75 cr => 'l3', # candidate
76 pr => 'l3', # proposed
77 rec => 'l5', # recommendation
79 ietf => 'l0', # standard
80 other => 'l0', # non-w3
83 while (my ($browser, $row) = each %{ $caniuse->{agents} }) {
84 $versions{$browser} = [
85 sort { paddedver($a) cmp paddedver($b) } grep { defined }
91 <p id="intro">Alternate rendition of Fyrd's <a href="http://caniuse.com/">when can I use...</a> page
93 my ($canihas, $usage);
94 my $minusage = $get{threshold} // 1;
95 given ($get{usage} // 'wm') {
99 when (!m{ \A [a-z]\w+ (?:/\d[\d-]*\d)? \z }x) {
101 'Invalid browser usage data request',
102 'Identifier must be alphanumeric name or <q>0</q>.',
105 $canihas = do "data/browser/usage-$_.inc.pl" or do {
106 Alert('Browser usage data not found', $@ || $!);
110 my $ref = $canihas->{-title} || 'unknown';
111 $ref = showlink($ref, $_)
112 for $canihas->{-site} || $canihas->{-source} || ();
113 $ref .= " $_" for $canihas->{-date} || ();
114 print "\nwith $ref browser usage statistics";
118 if ($usage) { # first() does not work inside given >:(
119 # adapt version usage to actual support data
120 my %engineuse; # prefix => usage sum
121 for my $browser (keys %versions) {
122 my $row = $canihas->{$browser} // {};
123 my $verlist = $versions{$browser} or next;
124 if ($minusage and sum(values %$row) < $minusage) {
125 delete $versions{$browser};
128 my %supported = map { $_ => 1 } @$verlist;
130 # cascade unknown versions
131 $row->{$_} //= undef for @$verlist; # ensure stable keys during iteration
132 while (my ($version, $usage) = each %$row) {
133 next if defined $supported{$version};
134 my $next = first { paddedver($_) ge paddedver($version) } @$verlist
135 or warn("No fallback found for $browser v$version; $usage% ignored"), next;
136 $row->{$next} += $usage;
137 $row->{$version} = 0; # balance browser total
140 # build row list for each version
142 my @vershown; # $verlist replacement
143 my ($rowusage, @verrow) = (0); # replacement row tracking
145 push @verrow, $_; # queue each version
146 if (($rowusage += $row->{$_}) >= $minusage) {
147 push @vershown, [@verrow]; # add row
148 ($rowusage, @verrow) = (0); # reset row tracking
151 push @vershown, \@verrow if @verrow; # always add latest
152 @$verlist = @vershown;
155 @$verlist = map { [$_] } @$verlist;
158 # reusable aggregates (grouped by prefix (engine) and browser)
159 $engineuse{ $caniuse->{agents}->{$browser}->{prefix} } +=
160 $row->{-total} = sum(values %$row);
163 # order browser columns by usage grouped by engine
165 $engineuse{ $caniuse->{agents}->{$b}->{prefix} } <=>
166 $engineuse{ $caniuse->{agents}->{$a}->{prefix} }
168 $canihas->{$b}->{-total} <=> $canihas->{$a}->{-total}
172 # order browser columns by name grouped by engine
173 @{$_} = map { [$_] } @{$_} for values %versions;
175 $caniuse->{agents}->{$b}->{prefix} cmp
176 $caniuse->{agents}->{$a}->{prefix}
189 my $zero = $#$_ - 2; # baseline index
190 ($_->[$zero - 2] => .5), # past
191 ($_->[$zero - 1] => 10 ), # previous
192 ($_->[$zero + 2] => 0 ), # future
193 ($_->[$zero + 1] => .5), # next
194 ($_->[$zero ] => 30 ), # current
195 } $caniuse->{agents}->{$_}->{versions}
198 }; # fallback hash based on release semantics
200 my $usagepct = 1; # score multiplier for 0..100 result
201 # normalise usage percentage to only include shown browsers
202 $usagepct = 100.01 / featurescore({ # yes for every possible version
203 map { $_ => { map {$_ => 'y'} map { @{$_} } @{$versions{$_}} } } keys %versions
206 print '<table class="mapped">';
207 print '<col span="3">'; # should match first thead row
208 printf '<colgroup span="%d">', scalar @{ $versions{$_} } for @browsers;
209 say '</colgroup><col>';
211 my $header = join('',
213 '<th colspan="3" rowspan="2">feature',
215 my $name = $caniuse->{agents}->{$_}->{browser};
216 sprintf('<th colspan="%d" class="%s" title="%s">%s',
217 scalar @{ $versions{$_} },
218 join(' ', map {"b-a-$_"} grep {$_}
219 $_, @{ $caniuse->{agents}->{$_} }{'prefix', 'type'},
222 sprintf('%.1f%%', $canihas->{$_}->{-total} * $usagepct),
226 length $name <= (3 * @{ $versions{$_} }) ? $name
227 : $caniuse->{agents}->{$_}->{abbr};
233 print '<thead>', $header;
234 # preceding row without any colspan to work around gecko bug
236 for my $browser (@browsers) {
237 for (@{ $versions{$browser} }) {
238 my $lastver = $_->[-1];
239 my $release = $caniuse->{agents}->{$browser}->{verrelease}->{$lastver};
240 my $future = defined $release;
241 printf('<td title="%s"%s>%s',
243 sprintf('%.1f%%', sum(@{ $canihas->{$browser} }{@$_}) * $usagepct),
244 $future ? 'development' : (),
245 'version ' . join(', ', @{$_}),
247 $future && ' class="ex"',
248 showversions($lastver),
253 say '<tfoot>', $header;
255 # prefix indicates browser family; count adjacent families
256 my (@families, %familycount);
257 for my $browser (@browsers) {
258 my $family = $caniuse->{agents}->{$browser}->{prefix};
259 push @families, $family unless $familycount{$family};
260 $familycount{$family} += @{ $versions{$browser} };
263 print "\n", '<tr class="cat">';
264 printf '<th colspan="%d">%s', $familycount{$_}, $_ for @families;
269 # relative amount of support for given feature
271 if (my $row = shift) {
273 while (my ($browser, $versions) = each %$row) {
274 ref $versions eq 'HASH' or next;
275 while (my ($version, $status) = each %$versions) {
276 $status =~ s/\h\#\d+//g;
277 $rank += ($canihas->{$browser}->{$version} || .001) * $PSTATS{$status};
283 while (my ($browser, $vercols) = each %versions) {
284 my $div = 0; # multiplier exponent (decreased to lower value)
285 my @vers = map { $row->{$browser}->{$_} } @$vercols;
286 if (my $current = $caniuse->{agents}->{$browser}->{versions}->[-3]) {
287 my @future; # find upcoming releases (after current)
288 for (reverse @$vercols) {
289 last if $_ eq $current;
290 push @future, pop @vers;
291 $_ eq 'u' and $_ = $vers[-1] for $future[-1]; # inherit latest value if unknown
293 splice @vers, -1, 0, @future; # move ahead to decrease precedence
295 $rank += $PSTATS{$_} * 2**($div--) for reverse @vers;
304 s/\r\n?/\n/g; # windows returns
305 s/\h* $//gmx; # trailing whitespace
306 s/(?<= [^.\n]) $/./gmx; # consistently end each line by a period
308 s{ ` ([^`]*) ` }{<code>$1</code>}gx;
309 s{ \[ ([^]]*) \] \( ([^)]*) \) }{<a href="$2">$1</a>}gx;
318 s{ \[ ([^]]*) \] \( [^)]* \) }{$1}gx; # strip link urls
325 my $row = $caniuse->{data}->{$id};
327 for ($row->{categories}) {
328 my $cell = $_ ? lc $_->[0] : '-';
329 $cell =~ s/ api$//; # trim unessential fluff in 'js api'
330 printf '<th title="%s">%s', join(' + ', @$_), $cell;
334 sprintf('<a href="%s" onclick="%s">%s</a>',
336 sprintf("try { %s; return false } catch(err) { return true }",
337 "document.getElementById('$id').classList.toggle('target')",
342 print '<div class=aside>';
344 for formatnotes($row->{description}, $row->{notes} || ());
345 if (my %notes = %{ $row->{notes_by_num} }) {
346 say '<p>Browser-specific notes:';
347 say "<br>#$_: ", formatnotes($notes{$_}) for sort keys %notes;
350 printf 'Resources: %s.', join(', ', map {
351 showlink($_->{title}, $_->{url})
352 } @$_) for grep { @$_ } $row->{links} // ();
353 printf '<br>Parent feature: %s.', join(', ', map {
354 showlink($caniuse->{data}->{$_}->{title}, "#$_")
355 } $_) for $row->{parent} || ();
361 my $row = $caniuse->{data}->{$id};
363 for ($row->{status}) {
364 my $cell = $_ // '-';
365 $cell = showlink($cell, $_) for $row->{spec} // ();
366 printf '<td title="%s" class="l %s">%s',
367 $caniuse->{statuses}->{$_}, $CSTATUS{$_} // '', $cell;
372 my ($id, $browser) = @_;
373 my $feature = $caniuse->{data}->{$id};
374 my $data = $feature->{stats}->{$browser};
375 if (ref $data eq 'ARRAY') {
376 # special case for unsupported
377 my $release = $caniuse->{agents}->{$browser}->{verrelease};
379 map { $_ => defined $release->{$_} ? 'u' : 'n' } keys %$release
384 for my $ver (@{ $versions{$browser} }, undef) {
386 !defined $ver ? undef : # last column if nameless
387 ref $data ne 'HASH' ? '' : # unclassified if no support hash
388 $data->{ $ver->[-1] } // $prev # known or inherit from predecessor
389 // (grep { defined } @{$data}{ map { $_->[0] } @{ $versions{$browser} } })[0]
390 ~~ 'n' && 'n' # first known version is unsupported
393 unless (!defined $prev or $prev ~~ $compare) {
394 my $usage = sum(@{ $canihas->{$browser} }{ map { @{$_} } @span });
396 # strip #\d note references from support class
398 push @notes, $feature->{notes_by_num}->{$1}
399 while $prev =~ s/\h \# (\d+) \b//x;
401 # prepare version hover details
402 my $title = sprintf('%.1f%% %s', $usage * $usagepct, join(', ',
403 map { ref $_ eq 'CODE' ? $_->($browser, $span[0]->[0]) : $_ }
404 map { $DSTATS{$_} // () }
405 map { split / /, $_ }
408 $title .= "\n$_" for notestotitle(@notes);
410 printf('<td class="%s" colspan="%d" title="%s">%s',
413 !$usage ? ('p0') : ('p',
414 sprintf('p%01d', $usage * ($usagepct - .0001) / 10),
415 sprintf('p%02d', $usage * ($usagepct - .0001)),
420 showversions($span[0]->[0], @span > 1 ? ($span[-1]->[-1]) : ()),
425 push @span, $ver && [ grep { $data->{ $_ } eq $data->{ $ver->[-1] } } @{$ver} ];
432 print '<td>', int featurescore($caniuse->{data}->{$id}->{stats}) * $usagepct;
437 featurescore($caniuse->{data}->{$b}->{stats})
438 <=> featurescore($caniuse->{data}->{$a}->{stats})
439 } keys %{ $caniuse->{data} }) {
440 $caniuse->{data}->{$id}->{stats} or next; # skip metadata [summary]
441 printf '<tr id="%s">', $id;
444 saybrowsercols($id, $_) for @browsers;
452 # normalised version number comparable as string (cmp)
453 shift =~ /(?:.*-|^)(\d*)(.*)/;
454 # matched (major)(.minor) of last value in range (a-B)
455 return sprintf('%02d', $1 || 99) . $2;
459 my @span = ($_[0], @_>1 ? $_[-1] : ());
460 s/-.*// for $span[0];
466 return join('‒', @span);
473 <table class="glyphs"><tr>
474 <td class="X l5">supported
475 <td class="X l3">partial
476 <td class="X l2">optional
477 <td class="X l1">missing
478 <td class="X l0">unknown
479 <td class="X ex">prefixed
482 <p><: if ($usage) { :>
484 <span class=" p0">0</span> -
485 <span class="p p0 p00">.01</span> -
486 <span class="p p0 p05">1-9</span> -
487 <span class="p p1">10</span> -
488 <span class="p p2">20</span> -
489 <span class="p p5">majority</span>
491 <table class="glyphs"><tr>
492 <td class="p p1">previous version</td>
493 <td class="p p3">current</td>
494 <td class="p p0 p00">upcoming (within months)</td>
495 <td class=" p0">future (within a year)</td>
500 <ul class="legend legend-set">
501 <li>default <strong>style</strong> is
502 <:= defined $get{style} && 'set to ' :><em><:= $style :></em>
503 <li><strong>usage</strong> source is
504 <:= !defined $get{usage} && 'default ' :><:= defined $usage ? "<em>$usage</em>" : 'not included (<em>0</em>)' :>
505 <li>usage <strong>threshold</strong> is
506 <:= defined $get{threshold} && 'changed to ' :><em><:= $minusage :>%</em>
511 <script type="text/javascript" src="/searchlocal.js"></script>
512 <script type="text/javascript"> prependsearch(document.getElementById('intro')) </script>