4 use List::Util qw(sum max first);
7 title => 'browser compatibility cheat sheet',
10 "Compatibility table of new web features (HTML5, CSS3, SVG, Javascript)",
11 "comparing support and usage share for all popular browser versions.",
14 web browser support compatibility usage available feature
15 html html5 css css3 svg javascript js dom mobile
16 ie internet explorer firefox chrome safari webkit opera
18 stylesheet => [qw'circus dark mono red light'],
19 data => ['browser-support.inc.pl'],
22 say "<h1>Browser compatibility</h1>\n";
24 my $caniuse = do 'browser-support.inc.pl' or die $! || $@;
26 # mark last three (future) versions as unreleased, ensure current isn't
28 $_->[-1] => 0, $_->[-2] => 0, $_->[-3] => 0,
31 } for values %{ $caniuse->{agents} };
52 p => 'plugin required',
53 j => 'javascript required',
56 d => 'disabled by default',
60 (map "-$_-", $caniuse->{agents}->{$_[0]}->{prefix} // ()),
65 unoff => 'l1', # unofficial
67 cr => 'l4', # candidate
68 pr => 'l4', # proposed
69 rec => 'l5', # recommendation
70 other => 'l2', # non-w3
71 ietf => 'l5', # standard
74 if (my ($somerow) = values %{ $caniuse->{data} }) {
75 while (my ($browser, $row) = each %{ $somerow->{stats} }) {
76 $versions{$browser} = [ sort { paddedver($a) cmp paddedver($b) } keys %$row ];
81 <p id="intro">Alternate rendition of Fyrd's <a href="http://caniuse.com/">when can I use...</a> page
83 my ($canihas, $usage);
84 my $minusage = $get{threshold} // .5;
85 given ($get{usage} // 'wm') {
89 when (!/^[a-z][\w-]+$/) {
90 printf "<p>Invalid browser usage data request: <em>%s</em>",
91 'identifier must be alphanumeric name or <q>0</q>';
93 $canihas = do "browser-usage-$_.inc.pl" or do {
94 printf "<p>Browser usage data not found: <em>%s</em>", $! || $@;
98 my $ref = $canihas->{-title} || 'unknown';
99 $ref = sprintf '<a href="%s">%s</a>', $_, $ref
100 for $canihas->{-site} || $canihas->{-source} || ();
101 $ref .= " $_" for $canihas->{-date} || ();
102 print "\nwith $ref browser usage statistics";
106 if ($usage) { # first() does not work inside given >:(
107 # adapt version usage to actual support data
108 my %engineuse; # prefix => usage sum
109 for my $browser (keys %versions) {
110 my $row = $canihas->{$browser} // {};
111 my $verlist = $versions{$browser} or next;
112 if ($minusage and sum(values %$row) < $minusage) {
113 delete $versions{$browser};
116 my %supported = map { $_ => 1 } @$verlist;
118 # cascade unknown versions
119 $row->{$_} //= undef for @$verlist; # ensure stable keys during iteration
120 while (my ($version, $usage) = each %$row) {
121 next if defined $supported{$version};
122 my $next = first { paddedver($_) ge paddedver($version) } @$verlist
123 or warn("No fallback found for $browser v$version; $usage% ignored"), next;
124 $row->{$next} += $usage;
125 $row->{$version} = 0; # balance browser total
128 # build row list for each version
130 my @vershown; # $verlist replacement
131 my ($rowusage, @verrow) = (0); # replacement row tracking
133 push @verrow, $_; # queue each version
134 if (($rowusage += $row->{$_}) >= $minusage) {
135 push @vershown, [@verrow]; # add row
136 ($rowusage, @verrow) = (0); # reset row tracking
139 push @vershown, \@verrow if @verrow; # always add latest
140 @$verlist = @vershown;
143 @$verlist = map { [$_] } @$verlist;
146 # reusable aggregates (grouped by prefix (engine) and browser)
147 $engineuse{ $caniuse->{agents}->{$browser}->{prefix} } +=
148 $row->{-total} = sum(values %$row);
151 # order browser columns by usage grouped by engine
153 $engineuse{ $caniuse->{agents}->{$b}->{prefix} } <=>
154 $engineuse{ $caniuse->{agents}->{$a}->{prefix} }
156 $canihas->{$b}->{-total} <=> $canihas->{$a}->{-total}
160 # order browser columns by name grouped by engine
162 $caniuse->{agents}->{$b}->{prefix} cmp
163 $caniuse->{agents}->{$a}->{prefix}
176 my $zero = $#$_ - 2; # baseline index
177 ($_->[$zero - 2] => .5), # past
178 ($_->[$zero - 1] => 10 ), # previous
179 ($_->[$zero + 2] => 0 ), # future
180 ($_->[$zero + 1] => .5), # next
181 ($_->[$zero ] => 30 ), # current
182 } $caniuse->{agents}->{$_}->{versions}
185 }; # fallback hash based on release semantics
186 my $usagemax = (max(map { ref $_ eq 'HASH' && sum(values %$_) } values %$canihas) // 1) / 100;
188 my $usagepct = 1; # score multiplier for 0..100 result
189 # normalise usage percentage to only include shown browsers
190 $usagepct = 100.01 / featurescore({ # yes for every possible version
191 map { $_ => { map {$_ => 'y'} map { @{$_} } @{$versions{$_}} } } keys %versions
194 print '<table class="mapped">';
195 print '<col span="3">'; # should match first thead row
196 printf '<colgroup span="%d">', scalar @{ $versions{$_} } for @browsers;
197 say '</colgroup><col>';
199 my $header = join('',
201 '<th colspan="3">feature',
203 my $name = $caniuse->{agents}->{$_}->{browser};
204 sprintf('<th colspan="%d" class="%s" title="%s">%s',
205 scalar @{ $versions{$_} },
206 join(' ', map {"b-a-$_"} grep {$_}
207 $_, @{ $caniuse->{agents}->{$_} }{'prefix', 'type'},
210 sprintf('%.1f%%', $canihas->{$_}->{-total} * $usagepct),
214 length $name < 3 + @{ $versions{$_} }*2 ? $name
215 : $caniuse->{agents}->{$_}->{abbr};
221 print '<thead>', $header;
222 # preceding row without any colspan to work around gecko bug
225 for my $browser (@browsers) {
226 for (@{ $versions{$browser} }) {
227 my $lastver = $_->[-1];
228 my $release = $caniuse->{agents}->{$browser}->{verrelease}->{$lastver};
229 my $future = defined $release;
230 printf('<td title="%s"%s>%s',
232 sprintf('%.1f%%', sum(@{ $canihas->{$browser} }{@$_}) * $usagepct),
233 $future ? 'development' : (),
234 'version ' . join(', ', @{$_}),
236 $future && ' class="ex"',
237 showversions($lastver),
243 say '<tfoot>', $header;
245 # prefix indicates browser family; count adjacent families
246 my (@families, %familycount);
247 for my $browser (@browsers) {
248 my $family = $caniuse->{agents}->{$browser}->{prefix};
249 push @families, $family unless $familycount{$family};
250 $familycount{$family} += @{ $versions{$browser} };
253 print "\n", '<tr class="cat">';
255 printf '<th colspan="%d">%s', $familycount{$_}, $_ for @families;
261 # relative amount of support for given feature
262 state $statspts = { y=>1, 'y x'=>1, a=>.5, 'a x'=>.5, j=>.2, 'p j'=>.2, 'p p'=>.2, p=>.1 };
264 if (my $row = shift) {
266 while (my ($browser, $versions) = each %$row) {
267 ref $versions eq 'HASH' or next;
268 while (my ($version, $_) = each %$versions) {
269 $rank += ($canihas->{$browser}->{$version} || .001) * $statspts->{$_};
275 while (my ($browser, $vercols) = each %versions) {
276 my $div = 0; # multiplier exponent (decreased to lower value)
277 my @vers = map { $row->{$browser}->{$_} } @$vercols;
278 if (my $current = $caniuse->{agents}->{$browser}->{versions}->[-3]) {
279 my @future; # find upcoming releases (after current)
280 for (reverse @$vercols) {
281 last if $_ eq $current;
282 push @future, pop @vers;
283 $_ eq 'u' and $_ = $vers[-1] for $future[-1]; # inherit latest value if unknown
285 splice @vers, -1, 0, @future; # move ahead to decrease precedence
287 $rank += $statspts->{$_} * 2**($div--) for reverse @vers;
295 my $row = $caniuse->{data}->{$id};
297 for ($row->{categories}) {
298 my $cell = $_ ? lc $_->[0] : '-';
299 $cell =~ s/ api$//; # trim unessential fluff in 'js api'
300 printf '<th title="%s">%s', join(' + ', @$_), $cell;
304 sprintf('<a href="%s" onclick="%s">%s</a>',
306 sprintf("try { %s; return false } catch(err) { return true }",
307 "document.getElementById('$id').classList.toggle('target')",
312 print '<div class=aside>';
313 s/\.?$/./, print "<p>$_</p>" for map { ref $_ ? @$_ : $_ || () }
314 Entity($row->{description}),
315 map { s/\s*\n/\n<br>/g; $_ } $row->{notes};
316 printf 'Resources: %s.', join(', ', map {
317 sprintf '<a href="%s">%s</a>', EscapeHTML($_->{url}), $_->{title}
318 } @$_) for grep { @$_ } $row->{links} // ();
319 printf '<br>Parent feature: %s.', join(', ', map {
320 sprintf '<a href="%s">%s</a>', EscapeHTML("#$_"), $caniuse->{data}->{$_}->{title}
321 } $_) for $row->{parent} || ();
327 my $row = $caniuse->{data}->{$id};
329 for ($row->{status}) {
330 my $cell = $_ // '-';
331 $cell = sprintf '<a href="%s">%s</a>', $_, $cell for $row->{spec} // ();
332 printf '<td title="%s" class="l %s">%s',
333 $caniuse->{statuses}->{$_}, $CSTATUS{$_} // '', $cell;
338 my ($id, $browser) = @_;
339 my $feature = $caniuse->{data}->{$id};
340 my $data = $feature->{stats}->{$browser};
341 if (ref $data eq 'ARRAY') {
342 # special case for unsupported
343 my $release = $caniuse->{agents}->{$browser}->{verrelease};
345 map { $_ => defined $release->{$_} ? 'u' : 'n' } keys %$release
350 for my $ver (@{ $versions{$browser} }, undef) {
352 !defined $ver ? undef : # last column if nameless
353 ref $data ne 'HASH' ? '' : # unclassified if no support hash
354 $data->{ $ver->[-1] } // $prev # known or inherit from predecessor
355 // (grep { defined } @{$data}{ map { $_->[0] } @{ $versions{$browser} } })[0]
356 ~~ 'n' && 'n' # first known version is unsupported
359 unless (!defined $prev or $prev ~~ $compare) {
360 my $usage = sum(@{ $canihas->{$browser} }{ map { @{$_} } @span });
362 # strip #\d note references from support class
364 push @notes, $feature->{notes_by_num}->{$1}
365 while $prev =~ s/\h \# (\d+) \b//x;
367 # prepare version hover details
368 my $title = sprintf('%.1f%% %s', $usage * $usagepct, join(', ',
369 map { ref $_ eq 'CODE' ? $_->($browser) : $_ }
370 map { $DSTATS{$_} // () }
371 map { split / /, $_ }
374 $title .= "\n".EscapeHTML($_) for @notes;
376 printf('<td class="%s" colspan="%d" title="%s">%s',
379 !$usage ? ('p0') : ('p',
380 sprintf('p%01d', $usage * ($usagepct - .0001) / 10),
381 sprintf('p%02d', $usage * ($usagepct - .0001)),
383 sprintf('pp%02d', $usage / $usagemax),
387 showversions($span[0]->[0], @span > 1 ? ($span[-1]->[-1]) : ()),
392 push @span, $ver && [ grep { $data->{ $_ } eq $data->{ $ver->[-1] } } @{$ver} ];
399 print '<td>', int featurescore($caniuse->{data}->{$id}->{stats}) * $usagepct;
404 featurescore($caniuse->{data}->{$b}->{stats})
405 <=> featurescore($caniuse->{data}->{$a}->{stats})
406 } keys %{ $caniuse->{data} }) {
407 $caniuse->{data}->{$id}->{stats} or next; # skip metadata [summary]
408 printf '<tr id="%s">', $id;
411 saybrowsercols($id, $_) for @browsers;
419 # normalised version number comparable as string (cmp)
420 shift =~ /(?:.*-|^)(\d*)(.*)/;
421 # matched (major)(.minor) of last value in range (a-B)
422 return sprintf('%02d', $1 || 99) . $2;
426 my @span = ($_[0], @_>1 ? $_[-1] : ());
427 s/-.*// for $span[0];
433 return join('‒', @span);
440 <table class="glyphs"><tr>
441 <td class="X l5">supported
442 <td class="X l3">partial
443 <td class="X l2">optional
444 <td class="X l1">missing
445 <td class="X l0">unknown
446 <td class="X ex">prefixed
449 <p><: if ($usage) { :>
451 <span class=" p0">0</span> -
452 <span class="p p0 p00">.01</span> -
453 <span class="p p0 p05">1-9</span> -
454 <span class="p p1">10</span> -
455 <span class="p p2">20</span> -
456 <span class="p p5">majority</span>
458 <table class="glyphs"><tr>
459 <td class="p p1">previous version</td>
460 <td class="p p3">current</td>
461 <td class="p p0 p00">upcoming (within months)</td>
462 <td class=" p0">future (within a year)</td>
467 <ul class="legend legend-set">
468 <li>default <strong>style</strong> is
469 <:= defined $get{style} && 'set to ' :><em><:= $style :></em>
470 <li><strong>usage</strong> source is
471 <:= !defined $get{usage} && 'default ' :><:= defined $usage ? "<em>$usage</em>" : 'not included (<em>0</em>)' :>
472 <li>usage <strong>threshold</strong> is
473 <:= defined $get{threshold} && 'changed to ' :><em><:= $minusage :>%</em>
478 <script type="text/javascript" src="/searchlocal.js"></script>
479 <script type="text/javascript"> prependsearch(document.getElementById('intro')) </script>