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 => 'l3', # candidate
68 pr => 'l3', # proposed
69 rec => 'l5', # recommendation
71 ietf => 'l0', # standard
72 other => 'l0', # non-w3
75 if (my ($somerow) = values %{ $caniuse->{data} }) {
76 while (my ($browser, $row) = each %{ $somerow->{stats} }) {
77 $versions{$browser} = [ sort { paddedver($a) cmp paddedver($b) } keys %$row ];
82 <p id="intro">Alternate rendition of Fyrd's <a href="http://caniuse.com/">when can I use...</a> page
84 my ($canihas, $usage);
85 my $minusage = $get{threshold} // .5;
86 given ($get{usage} // 'wm') {
90 when (!/^[a-z][\w-]+$/) {
91 printf "<p>Invalid browser usage data request: <em>%s</em>",
92 'identifier must be alphanumeric name or <q>0</q>';
94 $canihas = do "browser-usage-$_.inc.pl" or do {
95 printf "<p>Browser usage data not found: <em>%s</em>", $! || $@;
99 my $ref = $canihas->{-title} || 'unknown';
100 $ref = sprintf '<a href="%s">%s</a>', $_, $ref
101 for $canihas->{-site} || $canihas->{-source} || ();
102 $ref .= " $_" for $canihas->{-date} || ();
103 print "\nwith $ref browser usage statistics";
107 if ($usage) { # first() does not work inside given >:(
108 # adapt version usage to actual support data
109 my %engineuse; # prefix => usage sum
110 for my $browser (keys %versions) {
111 my $row = $canihas->{$browser} // {};
112 my $verlist = $versions{$browser} or next;
113 if ($minusage and sum(values %$row) < $minusage) {
114 delete $versions{$browser};
117 my %supported = map { $_ => 1 } @$verlist;
119 # cascade unknown versions
120 $row->{$_} //= undef for @$verlist; # ensure stable keys during iteration
121 while (my ($version, $usage) = each %$row) {
122 next if defined $supported{$version};
123 my $next = first { paddedver($_) ge paddedver($version) } @$verlist
124 or warn("No fallback found for $browser v$version; $usage% ignored"), next;
125 $row->{$next} += $usage;
126 $row->{$version} = 0; # balance browser total
129 # build row list for each version
131 my @vershown; # $verlist replacement
132 my ($rowusage, @verrow) = (0); # replacement row tracking
134 push @verrow, $_; # queue each version
135 if (($rowusage += $row->{$_}) >= $minusage) {
136 push @vershown, [@verrow]; # add row
137 ($rowusage, @verrow) = (0); # reset row tracking
140 push @vershown, \@verrow if @verrow; # always add latest
141 @$verlist = @vershown;
144 @$verlist = map { [$_] } @$verlist;
147 # reusable aggregates (grouped by prefix (engine) and browser)
148 $engineuse{ $caniuse->{agents}->{$browser}->{prefix} } +=
149 $row->{-total} = sum(values %$row);
152 # order browser columns by usage grouped by engine
154 $engineuse{ $caniuse->{agents}->{$b}->{prefix} } <=>
155 $engineuse{ $caniuse->{agents}->{$a}->{prefix} }
157 $canihas->{$b}->{-total} <=> $canihas->{$a}->{-total}
161 # order browser columns by name grouped by engine
163 $caniuse->{agents}->{$b}->{prefix} cmp
164 $caniuse->{agents}->{$a}->{prefix}
177 my $zero = $#$_ - 2; # baseline index
178 ($_->[$zero - 2] => .5), # past
179 ($_->[$zero - 1] => 10 ), # previous
180 ($_->[$zero + 2] => 0 ), # future
181 ($_->[$zero + 1] => .5), # next
182 ($_->[$zero ] => 30 ), # current
183 } $caniuse->{agents}->{$_}->{versions}
186 }; # fallback hash based on release semantics
187 my $usagemax = (max(map { ref $_ eq 'HASH' && sum(values %$_) } values %$canihas) // 1) / 100;
189 my $usagepct = 1; # score multiplier for 0..100 result
190 # normalise usage percentage to only include shown browsers
191 $usagepct = 100.01 / featurescore({ # yes for every possible version
192 map { $_ => { map {$_ => 'y'} map { @{$_} } @{$versions{$_}} } } keys %versions
195 print '<table class="mapped">';
196 print '<col span="3">'; # should match first thead row
197 printf '<colgroup span="%d">', scalar @{ $versions{$_} } for @browsers;
198 say '</colgroup><col>';
200 my $header = join('',
202 '<th colspan="3">feature',
204 my $name = $caniuse->{agents}->{$_}->{browser};
205 sprintf('<th colspan="%d" class="%s" title="%s">%s',
206 scalar @{ $versions{$_} },
207 join(' ', map {"b-a-$_"} grep {$_}
208 $_, @{ $caniuse->{agents}->{$_} }{'prefix', 'type'},
211 sprintf('%.1f%%', $canihas->{$_}->{-total} * $usagepct),
215 length $name < 3 + @{ $versions{$_} }*2 ? $name
216 : $caniuse->{agents}->{$_}->{abbr};
222 print '<thead>', $header;
223 # preceding row without any colspan to work around gecko bug
226 for my $browser (@browsers) {
227 for (@{ $versions{$browser} }) {
228 my $lastver = $_->[-1];
229 my $release = $caniuse->{agents}->{$browser}->{verrelease}->{$lastver};
230 my $future = defined $release;
231 printf('<td title="%s"%s>%s',
233 sprintf('%.1f%%', sum(@{ $canihas->{$browser} }{@$_}) * $usagepct),
234 $future ? 'development' : (),
235 'version ' . join(', ', @{$_}),
237 $future && ' class="ex"',
238 showversions($lastver),
244 say '<tfoot>', $header;
246 # prefix indicates browser family; count adjacent families
247 my (@families, %familycount);
248 for my $browser (@browsers) {
249 my $family = $caniuse->{agents}->{$browser}->{prefix};
250 push @families, $family unless $familycount{$family};
251 $familycount{$family} += @{ $versions{$browser} };
254 print "\n", '<tr class="cat">';
256 printf '<th colspan="%d">%s', $familycount{$_}, $_ for @families;
262 # relative amount of support for given feature
263 state $statspts = { y=>1, 'y x'=>1, a=>.5, 'a x'=>.5, j=>.2, 'p j'=>.2, 'p p'=>.2, p=>.1 };
265 if (my $row = shift) {
267 while (my ($browser, $versions) = each %$row) {
268 ref $versions eq 'HASH' or next;
269 while (my ($version, $_) = each %$versions) {
270 $rank += ($canihas->{$browser}->{$version} || .001) * $statspts->{$_};
276 while (my ($browser, $vercols) = each %versions) {
277 my $div = 0; # multiplier exponent (decreased to lower value)
278 my @vers = map { $row->{$browser}->{$_} } @$vercols;
279 if (my $current = $caniuse->{agents}->{$browser}->{versions}->[-3]) {
280 my @future; # find upcoming releases (after current)
281 for (reverse @$vercols) {
282 last if $_ eq $current;
283 push @future, pop @vers;
284 $_ eq 'u' and $_ = $vers[-1] for $future[-1]; # inherit latest value if unknown
286 splice @vers, -1, 0, @future; # move ahead to decrease precedence
288 $rank += $statspts->{$_} * 2**($div--) for reverse @vers;
296 my $row = $caniuse->{data}->{$id};
298 for ($row->{categories}) {
299 my $cell = $_ ? lc $_->[0] : '-';
300 $cell =~ s/ api$//; # trim unessential fluff in 'js api'
301 printf '<th title="%s">%s', join(' + ', @$_), $cell;
305 sprintf('<a href="%s" onclick="%s">%s</a>',
307 sprintf("try { %s; return false } catch(err) { return true }",
308 "document.getElementById('$id').classList.toggle('target')",
313 print '<div class=aside>';
314 s/\.?$/./, print "<p>$_</p>" for map { ref $_ ? @$_ : $_ || () }
315 Entity($row->{description}),
316 map { s/\s*\n/\n<br>/g; $_ } $row->{notes};
317 printf 'Resources: %s.', join(', ', map {
318 sprintf '<a href="%s">%s</a>', EscapeHTML($_->{url}), $_->{title}
319 } @$_) for grep { @$_ } $row->{links} // ();
320 printf '<br>Parent feature: %s.', join(', ', map {
321 sprintf '<a href="%s">%s</a>', EscapeHTML("#$_"), $caniuse->{data}->{$_}->{title}
322 } $_) for $row->{parent} || ();
328 my $row = $caniuse->{data}->{$id};
330 for ($row->{status}) {
331 my $cell = $_ // '-';
332 $cell = sprintf '<a href="%s">%s</a>', $_, $cell for $row->{spec} // ();
333 printf '<td title="%s" class="l %s">%s',
334 $caniuse->{statuses}->{$_}, $CSTATUS{$_} // '', $cell;
339 my ($id, $browser) = @_;
340 my $feature = $caniuse->{data}->{$id};
341 my $data = $feature->{stats}->{$browser};
342 if (ref $data eq 'ARRAY') {
343 # special case for unsupported
344 my $release = $caniuse->{agents}->{$browser}->{verrelease};
346 map { $_ => defined $release->{$_} ? 'u' : 'n' } keys %$release
351 for my $ver (@{ $versions{$browser} }, undef) {
353 !defined $ver ? undef : # last column if nameless
354 ref $data ne 'HASH' ? '' : # unclassified if no support hash
355 $data->{ $ver->[-1] } // $prev # known or inherit from predecessor
356 // (grep { defined } @{$data}{ map { $_->[0] } @{ $versions{$browser} } })[0]
357 ~~ 'n' && 'n' # first known version is unsupported
360 unless (!defined $prev or $prev ~~ $compare) {
361 my $usage = sum(@{ $canihas->{$browser} }{ map { @{$_} } @span });
363 # strip #\d note references from support class
365 push @notes, $feature->{notes_by_num}->{$1}
366 while $prev =~ s/\h \# (\d+) \b//x;
368 # prepare version hover details
369 my $title = sprintf('%.1f%% %s', $usage * $usagepct, join(', ',
370 map { ref $_ eq 'CODE' ? $_->($browser) : $_ }
371 map { $DSTATS{$_} // () }
372 map { split / /, $_ }
375 $title .= "\n".EscapeHTML($_) for @notes;
377 printf('<td class="%s" colspan="%d" title="%s">%s',
380 !$usage ? ('p0') : ('p',
381 sprintf('p%01d', $usage * ($usagepct - .0001) / 10),
382 sprintf('p%02d', $usage * ($usagepct - .0001)),
384 sprintf('pp%02d', $usage / $usagemax),
388 showversions($span[0]->[0], @span > 1 ? ($span[-1]->[-1]) : ()),
393 push @span, $ver && [ grep { $data->{ $_ } eq $data->{ $ver->[-1] } } @{$ver} ];
400 print '<td>', int featurescore($caniuse->{data}->{$id}->{stats}) * $usagepct;
405 featurescore($caniuse->{data}->{$b}->{stats})
406 <=> featurescore($caniuse->{data}->{$a}->{stats})
407 } keys %{ $caniuse->{data} }) {
408 $caniuse->{data}->{$id}->{stats} or next; # skip metadata [summary]
409 printf '<tr id="%s">', $id;
412 saybrowsercols($id, $_) for @browsers;
420 # normalised version number comparable as string (cmp)
421 shift =~ /(?:.*-|^)(\d*)(.*)/;
422 # matched (major)(.minor) of last value in range (a-B)
423 return sprintf('%02d', $1 || 99) . $2;
427 my @span = ($_[0], @_>1 ? $_[-1] : ());
428 s/-.*// for $span[0];
434 return join('‒', @span);
441 <table class="glyphs"><tr>
442 <td class="X l5">supported
443 <td class="X l3">partial
444 <td class="X l2">optional
445 <td class="X l1">missing
446 <td class="X l0">unknown
447 <td class="X ex">prefixed
450 <p><: if ($usage) { :>
452 <span class=" p0">0</span> -
453 <span class="p p0 p00">.01</span> -
454 <span class="p p0 p05">1-9</span> -
455 <span class="p p1">10</span> -
456 <span class="p p2">20</span> -
457 <span class="p p5">majority</span>
459 <table class="glyphs"><tr>
460 <td class="p p1">previous version</td>
461 <td class="p p3">current</td>
462 <td class="p p0 p00">upcoming (within months)</td>
463 <td class=" p0">future (within a year)</td>
468 <ul class="legend legend-set">
469 <li>default <strong>style</strong> is
470 <:= defined $get{style} && 'set to ' :><em><:= $style :></em>
471 <li><strong>usage</strong> source is
472 <:= !defined $get{usage} && 'default ' :><:= defined $usage ? "<em>$usage</em>" : 'not included (<em>0</em>)' :>
473 <li>usage <strong>threshold</strong> is
474 <:= defined $get{threshold} && 'changed to ' :><em><:= $minusage :>%</em>
479 <script type="text/javascript" src="/searchlocal.js"></script>
480 <script type="text/javascript"> prependsearch(document.getElementById('intro')) </script>