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 matrix 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} // .7;
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" rowspan="2">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{$_} }) ? $name
216 : $caniuse->{agents}->{$_}->{abbr};
222 print '<thead>', $header;
223 # 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),
242 say '<tfoot>', $header;
244 # prefix indicates browser family; count adjacent families
245 my (@families, %familycount);
246 for my $browser (@browsers) {
247 my $family = $caniuse->{agents}->{$browser}->{prefix};
248 push @families, $family unless $familycount{$family};
249 $familycount{$family} += @{ $versions{$browser} };
252 print "\n", '<tr class="cat">';
253 printf '<th colspan="%d">%s', $familycount{$_}, $_ for @families;
258 # relative amount of support for given feature
259 state $statspts = { y=>1, 'y x'=>1, a=>.5, 'a x'=>.5, j=>.2, 'p j'=>.2, 'p p'=>.2, p=>.1 };
261 if (my $row = shift) {
263 while (my ($browser, $versions) = each %$row) {
264 ref $versions eq 'HASH' or next;
265 while (my ($version, $_) = each %$versions) {
266 $rank += ($canihas->{$browser}->{$version} || .001) * $statspts->{$_};
272 while (my ($browser, $vercols) = each %versions) {
273 my $div = 0; # multiplier exponent (decreased to lower value)
274 my @vers = map { $row->{$browser}->{$_} } @$vercols;
275 if (my $current = $caniuse->{agents}->{$browser}->{versions}->[-3]) {
276 my @future; # find upcoming releases (after current)
277 for (reverse @$vercols) {
278 last if $_ eq $current;
279 push @future, pop @vers;
280 $_ eq 'u' and $_ = $vers[-1] for $future[-1]; # inherit latest value if unknown
282 splice @vers, -1, 0, @future; # move ahead to decrease precedence
284 $rank += $statspts->{$_} * 2**($div--) for reverse @vers;
292 my $row = $caniuse->{data}->{$id};
294 for ($row->{categories}) {
295 my $cell = $_ ? lc $_->[0] : '-';
296 $cell =~ s/ api$//; # trim unessential fluff in 'js api'
297 printf '<th title="%s">%s', join(' + ', @$_), $cell;
301 sprintf('<a href="%s" onclick="%s">%s</a>',
303 sprintf("try { %s; return false } catch(err) { return true }",
304 "document.getElementById('$id').classList.toggle('target')",
309 print '<div class=aside>';
310 s/\.?$/./, print "<p>$_</p>" for map { ref $_ ? @$_ : $_ || () }
311 Entity($row->{description}),
312 map { s/\s*\n/\n<br>/g; $_ } $row->{notes};
313 printf 'Resources: %s.', join(', ', map {
314 sprintf '<a href="%s">%s</a>', EscapeHTML($_->{url}), $_->{title}
315 } @$_) for grep { @$_ } $row->{links} // ();
316 printf '<br>Parent feature: %s.', join(', ', map {
317 sprintf '<a href="%s">%s</a>', EscapeHTML("#$_"), $caniuse->{data}->{$_}->{title}
318 } $_) for $row->{parent} || ();
324 my $row = $caniuse->{data}->{$id};
326 for ($row->{status}) {
327 my $cell = $_ // '-';
328 $cell = sprintf '<a href="%s">%s</a>', $_, $cell for $row->{spec} // ();
329 printf '<td title="%s" class="l %s">%s',
330 $caniuse->{statuses}->{$_}, $CSTATUS{$_} // '', $cell;
335 my ($id, $browser) = @_;
336 my $feature = $caniuse->{data}->{$id};
337 my $data = $feature->{stats}->{$browser};
338 if (ref $data eq 'ARRAY') {
339 # special case for unsupported
340 my $release = $caniuse->{agents}->{$browser}->{verrelease};
342 map { $_ => defined $release->{$_} ? 'u' : 'n' } keys %$release
347 for my $ver (@{ $versions{$browser} }, undef) {
349 !defined $ver ? undef : # last column if nameless
350 ref $data ne 'HASH' ? '' : # unclassified if no support hash
351 $data->{ $ver->[-1] } // $prev # known or inherit from predecessor
352 // (grep { defined } @{$data}{ map { $_->[0] } @{ $versions{$browser} } })[0]
353 ~~ 'n' && 'n' # first known version is unsupported
356 unless (!defined $prev or $prev ~~ $compare) {
357 my $usage = sum(@{ $canihas->{$browser} }{ map { @{$_} } @span });
359 # strip #\d note references from support class
361 push @notes, $feature->{notes_by_num}->{$1}
362 while $prev =~ s/\h \# (\d+) \b//x;
364 # prepare version hover details
365 my $title = sprintf('%.1f%% %s', $usage * $usagepct, join(', ',
366 map { ref $_ eq 'CODE' ? $_->($browser) : $_ }
367 map { $DSTATS{$_} // () }
368 map { split / /, $_ }
371 $title .= "\n".EscapeHTML($_) for @notes;
373 printf('<td class="%s" colspan="%d" title="%s">%s',
376 !$usage ? ('p0') : ('p',
377 sprintf('p%01d', $usage * ($usagepct - .0001) / 10),
378 sprintf('p%02d', $usage * ($usagepct - .0001)),
380 sprintf('pp%02d', $usage / $usagemax),
384 showversions($span[0]->[0], @span > 1 ? ($span[-1]->[-1]) : ()),
389 push @span, $ver && [ grep { $data->{ $_ } eq $data->{ $ver->[-1] } } @{$ver} ];
396 print '<td>', int featurescore($caniuse->{data}->{$id}->{stats}) * $usagepct;
401 featurescore($caniuse->{data}->{$b}->{stats})
402 <=> featurescore($caniuse->{data}->{$a}->{stats})
403 } keys %{ $caniuse->{data} }) {
404 $caniuse->{data}->{$id}->{stats} or next; # skip metadata [summary]
405 printf '<tr id="%s">', $id;
408 saybrowsercols($id, $_) for @browsers;
416 # normalised version number comparable as string (cmp)
417 shift =~ /(?:.*-|^)(\d*)(.*)/;
418 # matched (major)(.minor) of last value in range (a-B)
419 return sprintf('%02d', $1 || 99) . $2;
423 my @span = ($_[0], @_>1 ? $_[-1] : ());
424 s/-.*// for $span[0];
430 return join('‒', @span);
437 <table class="glyphs"><tr>
438 <td class="X l5">supported
439 <td class="X l3">partial
440 <td class="X l2">optional
441 <td class="X l1">missing
442 <td class="X l0">unknown
443 <td class="X ex">prefixed
446 <p><: if ($usage) { :>
448 <span class=" p0">0</span> -
449 <span class="p p0 p00">.01</span> -
450 <span class="p p0 p05">1-9</span> -
451 <span class="p p1">10</span> -
452 <span class="p p2">20</span> -
453 <span class="p p5">majority</span>
455 <table class="glyphs"><tr>
456 <td class="p p1">previous version</td>
457 <td class="p p3">current</td>
458 <td class="p p0 p00">upcoming (within months)</td>
459 <td class=" p0">future (within a year)</td>
464 <ul class="legend legend-set">
465 <li>default <strong>style</strong> is
466 <:= defined $get{style} && 'set to ' :><em><:= $style :></em>
467 <li><strong>usage</strong> source is
468 <:= !defined $get{usage} && 'default ' :><:= defined $usage ? "<em>$usage</em>" : 'not included (<em>0</em>)' :>
469 <li>usage <strong>threshold</strong> is
470 <:= defined $get{threshold} && 'changed to ' :><em><:= $minusage :>%</em>
475 <script type="text/javascript" src="/searchlocal.js"></script>
476 <script type="text/javascript"> prependsearch(document.getElementById('intro')) </script>