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 two (future) versions as unreleased, ensure current isn't
27 map { $_->[-1] => 0, $_->[-2] => 0, $_->[-3] => undef } $_->{versions}
28 } for values %{ $caniuse->{agents} };
45 p => 'plugin required',
46 j => 'javascript required',
52 (map "-$_-", $caniuse->{agents}->{$_[0]}->{prefix} // ()),
57 unoff => 'l1', # unofficial
59 cr => 'l4', # candidate
60 pr => 'l4', # proposed
61 rec => 'l5', # recommendation
62 other => 'l2', # non-w3
63 ietf => 'l5', # standard
66 if (my ($somerow) = values %{ $caniuse->{data} }) {
67 while (my ($browser, $row) = each %{ $somerow->{stats} }) {
68 $versions{$browser} = [ sort { paddedver($a) cmp paddedver($b) } keys %$row ];
71 my @browsers = keys %versions;
74 <p id="intro">Alternate rendition of Fyrd's <a href="http://caniuse.com/">when can I use...</a> page
76 my ($canihas, $usage, $minusage);
77 given ($get{usage} // 'wm') {
81 when (!/^[a-z][\w-]+$/) {
82 printf "<p>Invalid browser usage data request: <em>%s</em>",
83 'identifier must be alphanumeric name or <q>0</q>';
85 $canihas = do "browser-usage-$_.inc.pl" or do {
86 printf "<p>Browser usage data not found: <em>%s</em>", $! || $@;
90 my $ref = $canihas->{-title} || 'unknown';
91 $ref = sprintf '<a href="%s">%s</a>', $_, $ref
92 for $canihas->{-site} || $canihas->{-source} || ();
93 $ref .= " $_" for $canihas->{-date} || ();
94 print "\nwith $ref browser usage statistics";
96 if ($usage) { # first() does not work inside given >:(
97 # adapt version usage to actual support data
98 my %engineuse; # prefix => usage sum
99 for my $browser (keys %versions) {
100 my $row = $canihas->{$browser} // {};
101 my $verlist = $versions{$browser} or next;
102 my %supported = map { $_ => 1 } @$verlist;
104 # cascade unknown versions
105 $row->{$_} //= undef for @$verlist; # ensure stable keys during iteration
106 while (my ($version, $usage) = each %$row) {
107 next if defined $supported{$version};
108 my $next = first { paddedver($_) ge paddedver($version) } @$verlist
109 or warn("No fallback found for $browser v$version; $usage% ignored"), next;
110 $row->{$next} += $usage;
111 $row->{$version} = 0; # balance browser total
114 # build row list for each version
115 if ($minusage = $get{threshold} // .5) {
116 my @vershown; # $verlist replacement
117 my ($rowusage, @verrow) = (0); # replacement row tracking
119 push @verrow, $_; # queue each version
120 if (($rowusage += $row->{$_}) >= $minusage) {
121 push @vershown, [@verrow]; # add row
122 ($rowusage, @verrow) = (0); # reset row tracking
125 push @vershown, \@verrow if @verrow; # always add latest
126 @$verlist = @vershown;
129 @$verlist = map { [$_] } @$verlist;
132 # reusable aggregates (grouped by prefix (engine) and browser)
133 $engineuse{ $caniuse->{agents}->{$browser}->{prefix} } +=
134 $row->{-total} = sum(values %$row);
137 # order browser columns by usage grouped by engine
139 $engineuse{ $caniuse->{agents}->{$b}->{prefix} } <=>
140 $engineuse{ $caniuse->{agents}->{$a}->{prefix} }
142 $canihas->{$b}->{-total} <=> $canihas->{$a}->{-total}
146 # order browser columns by name grouped by engine
148 $caniuse->{agents}->{$b}->{prefix} cmp
149 $caniuse->{agents}->{$a}->{prefix}
162 my $zero = $#$_ - 2; # baseline index
163 ($_->[$zero - 2] => .5), # past
164 ($_->[$zero - 1] => 10 ), # previous
165 ($_->[$zero + 2] => 0 ), # future
166 ($_->[$zero + 1] => .5), # next
167 ($_->[$zero ] => 30 ), # current
168 } $caniuse->{agents}->{$_}->{versions}
171 }; # fallback hash based on release semantics
172 my $usagemax = (max(map { ref $_ eq 'HASH' && sum(values %$_) } values %$canihas) // 1) / 100;
174 my $usagepct = 1; # score multiplier for 0..100 result
175 # normalise usage percentage to only include shown browsers
176 $usagepct = 100.01 / featurescore({ # yes for every possible version
177 map { $_ => { map {$_ => 'y'} map { @{$_} } @{$versions{$_}} } } keys %versions
180 print '<table class="mapped">';
181 print '<col span="3">'; # should match first thead row
182 printf '<colgroup span="%d">', scalar @{ $versions{$_} } for @browsers;
183 say '</colgroup><col>';
185 my $header = join('',
187 '<th colspan="3">feature',
189 my $name = $caniuse->{agents}->{$_}->{browser};
190 sprintf('<th colspan="%d" class="%s" title="%s">%s',
191 scalar @{ $versions{$_} },
192 join(' ', map {"b-a-$_"} grep {$_}
193 $_, @{ $caniuse->{agents}->{$_} }{'prefix', 'type'},
196 sprintf('%.1f%%', $canihas->{$_}->{-total} * $usagepct),
200 length $name < 3 + @{ $versions{$_} }*2 ? $name
201 : $caniuse->{agents}->{$_}->{abbr};
207 print '<thead>', $header;
208 # preceding row without any colspan to work around gecko bug
211 for my $browser (@browsers) {
212 for (@{ $versions{$browser} }) {
213 my $lastver = $_->[-1];
214 my $release = $caniuse->{agents}->{$browser}->{verrelease}->{$lastver};
215 my $future = defined $release;
216 printf('<td title="%s"%s>%s',
218 sprintf('%.1f%%', sum(@{ $canihas->{$browser} }{@$_}) * $usagepct),
219 $future ? 'development' : (),
220 'version ' . join(', ', @{$_}),
222 $future && ' class="ex"',
223 showversions($lastver),
229 say '<tfoot>', $header, '</tfoot>';
232 # relative amount of support for given feature
233 state $statspts = { y=>1, 'y x'=>1, a=>.5, 'a x'=>.5, j=>.2, 'p j'=>.2, 'p p'=>.2, p=>.1 };
235 if (my $row = shift) {
237 while (my ($browser, $versions) = each %$row) {
238 ref $versions eq 'HASH' or next;
239 while (my ($version, $_) = each %$versions) {
240 $rank += ($canihas->{$browser}->{$version} || .001) * $statspts->{$_};
246 while (my ($browser, $vercols) = each %versions) {
247 my $div = 0; # multiplier exponent (decreased to lower value)
248 my @vers = map { $row->{$browser}->{$_} } @$vercols;
249 if (my $current = $caniuse->{agents}->{$browser}->{versions}->[-3]) {
250 my @future; # find upcoming releases (after current)
251 for (reverse @$vercols) {
252 last if $_ eq $current;
253 push @future, pop @vers;
254 $_ eq 'u' and $_ = $vers[-1] for $future[-1]; # inherit latest value if unknown
256 splice @vers, -1, 0, @future; # move ahead to decrease precedence
258 $rank += $statspts->{$_} * 2**($div--) for reverse @vers;
266 my $row = $caniuse->{data}->{$id};
268 for ($row->{categories}) {
269 my $cell = $_ ? lc $_->[0] : '-';
270 $cell =~ s/ api$//; # trim unessential fluff in 'js api'
271 printf '<th title="%s">%s', join(' + ', @$_), $cell;
275 sprintf('<a href="%s" onclick="%s">%s</a>',
277 sprintf("try { %s; return false } catch(err) { return true }",
278 "document.getElementById('$id').classList.toggle('target')",
283 print '<div class=aside>';
284 s/\.?$/./, print "<p>$_</p>" for map { ref $_ ? @$_ : $_ || () }
285 Entity($row->{description}),
286 map { s/\s*\n/\n<br>/g; $_ } $row->{notes};
287 printf 'Resources: %s.', join(', ', map {
288 sprintf '<a href="%s">%s</a>', EscapeHTML($_->{url}), $_->{title}
289 } @$_) for grep { @$_ } $row->{links} // ();
290 printf '<br>Parent feature: %s.', join(', ', map {
291 sprintf '<a href="%s">%s</a>', EscapeHTML("#$_"), $caniuse->{data}->{$_}->{title}
292 } $_) for $row->{parent} || ();
298 my $row = $caniuse->{data}->{$id};
300 for ($row->{status}) {
301 my $cell = $_ // '-';
302 $cell = sprintf '<a href="%s">%s</a>', $_, $cell for $row->{spec} // ();
303 printf '<td title="%s" class="l %s">%s',
304 $caniuse->{statuses}->{$_}, $CSTATUS{$_} // '', $cell;
309 my ($id, $browser) = @_;
310 my $data = $caniuse->{data}->{$id}->{stats}->{$browser};
311 if (ref $data eq 'ARRAY') {
312 # special case for unsupported
313 my $release = $caniuse->{agents}->{$browser}->{verrelease};
315 map { $_ => defined $release->{$_} ? 'u' : 'n' } keys %$release
320 for my $ver (@{ $versions{$browser} }, undef) {
322 !defined $ver ? undef : # last column if nameless
323 ref $data ne 'HASH' ? '' : # unclassified if no support hash
324 $data->{ $ver->[-1] } // $prev # known or inherit from predecessor
325 // (grep { defined } @{$data}{ map { $_->[0] } @{ $versions{$browser} } })[0]
326 ~~ 'n' && 'n' # first known version is unsupported
329 unless (!defined $prev or $prev ~~ $compare) {
330 my $usage = sum(@{ $canihas->{$browser} }{ map { @{$_} } @span });
331 printf '<td class="%s" colspan="%d" title="%s">%s',
334 !$usage ? ('p0') : ('p',
335 sprintf('p%01d', $usage * ($usagepct - .0001) / 10),
336 sprintf('p%02d', $usage * ($usagepct - .0001)),
338 sprintf('pp%02d', $usage / $usagemax),
341 sprintf('%.1f%% %s', $usage * $usagepct, join(', ',
342 map { ref $_ eq 'CODE' ? $_->($browser) : $_ }
343 map { $DSTATS{$_} // () }
344 map { split / /, $_ }
347 showversions($span[0]->[0], @span > 1 ? ($span[-1]->[-1]) : ()),
351 push @span, $ver && [ grep { $data->{ $_ } eq $data->{ $ver->[-1] } } @{$ver} ];
358 print '<td>', int featurescore($caniuse->{data}->{$id}->{stats}) * $usagepct;
363 featurescore($caniuse->{data}->{$b}->{stats})
364 <=> featurescore($caniuse->{data}->{$a}->{stats})
365 } keys %{ $caniuse->{data} }) {
366 $caniuse->{data}->{$id}->{stats} or next; # skip metadata [summary]
367 printf '<tr id="%s">', $id;
370 saybrowsercols($id, $_) for @browsers;
378 # normalised version number comparable as string (cmp)
379 shift =~ /(?:.*-|^)(\d*)(.*)/;
380 # matched (major)(.minor) of last value in range (a-B)
381 return sprintf('%02d', $1 || 0) . $2;
385 my @span = ($_[0], @_>1 ? $_[-1] : ());
386 s/-.*// for $span[0];
392 return join('‒', @span);
399 <table class="glyphs"><tr>
400 <td class="X l5">supported
401 <td class="X l3">partial
402 <td class="X l2">external (js/plugin)
403 <td class="X l1">missing
404 <td class="X l0">unknown
405 <td class="X ex">prefixed
408 <p><: if ($usage) { :>
410 <span class=" p0">0</span> -
411 <span class="p p0 p00">.01</span> -
412 <span class="p p0 p05">1-9</span> -
413 <span class="p p1">10</span> -
414 <span class="p p2">20</span> -
415 <span class="p p5">majority</span>
417 <table class="glyphs"><tr>
418 <td class="p p1">previous version</td>
419 <td class="p p3">current</td>
420 <td class="p p0 p00">upcoming (within months)</td>
421 <td class=" p0">future (within a year)</td>
426 <ul class="legend legend-set">
427 <li>default <strong>style</strong> is
428 <:= defined $get{style} && 'set to ' :><em><:= $style :></em>
429 <li><strong>usage</strong> source is
430 <:= !defined $get{usage} && 'default ' :><:= defined $usage ? "<em>$usage</em>" : 'not included (<em>0</em>)' :>
431 <li>usage <strong>threshold</strong> is
432 <:= defined $get{threshold} && 'changed to ' :><em><:= $minusage :>%</em>
437 <script type="text/javascript" src="/searchlocal.js"></script>
438 <script type="text/javascript"> prependsearch(document.getElementById('intro')) </script>